# Demand signals report

`GET https://api.onekey.dev/v1/reports/demand-signals?topic=...` searches a topic on community platforms and counts how many of the returned posts look like someone wanting to buy, asking a question, or complaining, with the top examples of each. See [Reports](https://onekey.dev/docs/reports.md) for the shape and rules every report shares.

> [!NOTE] **Not enabled yet.** AI enrichment ships when a production model is connected; every number on this page comes from the pre-release test provider and will change. It is switched on per account, so nothing here describes real-world judgment quality yet. Where it is not enabled, a request that asks for it with the default `strict=true` is refused with `503 AI_UNAVAILABLE` at no charge; with `strict=false` you get the rows back unjudged. Enrichment is also switched on per platform: only the endpoints in the [table on the Labels page](https://onekey.dev/docs/labels.md#which-endpoints-accept-it) accept these parameters.

This report is AI-dependent and not enabled yet (see the note above): the counts are judgments. The searches, the volume per platform and the coverage are not; they are the same deterministic legs as any [multi-platform search](https://onekey.dev/docs/search-multi.md).

```bash
curl "https://api.onekey.dev/v1/reports/demand-signals?topic=mechanical%20keyboard&platforms=hackernews,devto,reddit&top=2" \
  -H "x-api-key: YOUR_API_KEY"
```

## Inputs

| Parameter | Values | What it does |
| --- | --- | --- |
| `topic` | text, required, up to 200 characters | The topic to search for. |
| `platforms` | comma list | Search exactly these. Default: the [pool](https://onekey.dev/docs/reports.md#default-pools-today), which is Bluesky, DEV Community, Hacker News, Lemmy, Reddit, Stack Exchange. |
| `max_platforms` | 1 to 12, default 6 | Cap on the platform list. |
| `min_p` | 0 to 1, default 0.5 | A row counts for a signal when its probability is at or above this. |
| `top` | 1 to 50, default 5 | How many examples per signal. |
| `relevance`, `threshold` | `score` or `filter`; 0 to 1 | Also judge each row against the topic. |
| `themes` | `true` | Add `stats.top_themes`. |
| `strict` | `true` (default) or `false` | What happens when nothing could be judged. |
| `dry_run` | `true` | See [Dry run](https://onekey.dev/docs/reports.md#dry-run). |

The report always labels `purchase_intent`, `question` and `complaint`, so `include` and `label` are refused with [`INVALID_PARAMS`](https://onekey.dev/docs/errors.md#invalid-params). Their price is metered per judged row on top of the legs.

The default pool is small because it needs searches that accept labels. You may name other platforms in `platforms`. Their rows are then counted but not judged (`labels.unsupported` lists them).

## The response

On top of the [shared shape](https://onekey.dev/docs/reports.md#the-shape-every-report-shares):

| Field | Meaning |
| --- | --- |
| `stats.rows` | Rows over the legs that answered. |
| `stats.volume_per_platform` | Rows per platform, `null` for a platform whose leg failed. |
| `stats.purchase_intent`, `stats.question`, `stats.complaint` | For each: `preset`, `threshold` (= `min_p`), `rows`, `judged`, `unjudged`, `count` (rows at or above `min_p`) and `share_of_judged` (`count` / `judged`). |
| `stats.top_themes` | The largest clusters when `themes=true`, else `null`. |
| `examples.<signal>` | The `top` rows by probability for each signal, with `platform`, `p` and the `item`. |
| `labels` | The [labels block](https://onekey.dev/docs/labels.md#the-response): `presets`, `labelled`, `extra_credits` and `methodology_version`. |

`share_of_judged` divides by the rows that were judged, not by all rows. A row that could not be judged is in `unjudged` and never counts for or against a signal.

## A recorded call

Recorded on the dev API with the pre-release test provider, shortened. Reddit failed and is shown as such:

```json
{
  "success": true,
  "data": {
    "report": "demand_signals",
    "methodology_version": "demand_signals/1",
    "inputs": {
      "topic": "mechanical keyboard", "platforms": ["hackernews", "devto", "reddit"], "selection": "explicit",
      "labels": ["purchase_intent", "question", "complaint"],
      "subqueries": { "hackernews": "mechanical keyboard", "devto": "mechanical", "reddit": "mechanical keyboard" },
      "adapted": { "devto": "first keyword only" }, "skipped": {},
      "min_p": 0.5, "top": 2, "themes": false, "relevance": null
    },
    "coverage": {
      "answered": ["hackernews", "devto"],
      "failed": [ { "platform": "reddit", "endpoint": "search_posts", "status": 502, "type": "UPSTREAM_BLOCKED" } ],
      "missing_fields": {}
    },
    "stats": {
      "rows": 20,
      "volume_per_platform": { "hackernews": 10, "devto": 10, "reddit": null },
      "purchase_intent": { "preset": "purchase_intent", "threshold": 0.5, "rows": 20, "judged": 20, "unjudged": 0, "count": 1, "share_of_judged": 0.05 },
      "question": { "preset": "question", "threshold": 0.5, "rows": 20, "judged": 20, "unjudged": 0, "count": 3, "share_of_judged": 0.15 },
      "complaint": { "preset": "complaint", "threshold": 0.5, "rows": 20, "judged": 20, "unjudged": 0, "count": 0, "share_of_judged": 0.0 },
      "top_themes": null
    },
    "examples": {
      "purchase_intent": [ { "platform": "devto", "p": 0.55, "item": { "content": { "id": "4561995", "title": "Product Sliding Guide Rail Design: ..." } } } ],
      "question": [ { "platform": "devto", "p": 0.92, "item": { "content": { "id": "4648836", "title": "How Do I Choose the Right Mechanical Contractor for a Project in KSA?" } } } ]
    },
    "labels": { "status": "complete", "presets": ["complaint", "purchase_intent", "question"], "rows": 20, "labelled": 20, "extra_credits": 19.695, "methodology_version": "fake/1" },
    "ai_methodology_version": "fake/1",
    "credits": { "legs": 10.0, "ai": 19.695, "total": 29.695 }
  },
  "metadata": { "recipe": "demand_signals", "credits_charged": 29.695, "extra_credits": 19.695 }
}
```

Read this the way the report asks you to. The DEV Community search is tag-shaped, so "mechanical keyboard" became the tag `mechanical` (visible in `inputs.subqueries` and `adapted`), and the examples are about mechanical contractors and guide rails, not keyboards. The test provider's `purchase_intent` is keyword matching on words such as "purchase", "buy" and "price". These counts prove that the plumbing works. They are not findings about keyboards.

## Costs

Measured on the pre-release test provider (synthetic usage, so it shows how the charge scales, not what production judgments will cost): 10 credits for the legs (the failed Reddit leg was refunded), 19.695 for judging 20 rows with 3 presets (0.98 per row), 29.695 in all. See [AI pricing](https://onekey.dev/docs/ai-pricing.md).

## When AI is not enabled

With the default `strict=true` the call is a free [`AI_UNAVAILABLE`](https://onekey.dev/docs/errors.md#ai-unavailable) before any fetch. With `strict=false` it answers at the legs' price only, with every count reporting `judged: 0` and empty `examples`.

## Limits

- Every count is an AI judgment. On the pre-release test provider they are keyword heuristics; nothing here has been run against a production model.
- The default pool is small (6 platforms today) because it needs searches that accept labels.
- Tag-style searches receive one keyword, which can change what a topic means. `inputs.adapted` shows it.
- Counts cover the first page of each platform's search, not everything ever posted about the topic.
- Reports are JSON only. A `Accept: text/event-stream` header is ignored, and a report is not yet a target for a [monitor](https://onekey.dev/docs/monitors.md).
