Docs
View as Markdown

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 for the shape and rules every report shares.

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.

shell
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#

ParameterValuesWhat it does
topictext, required, up to 200 charactersThe topic to search for.
platformscomma listSearch exactly these. Default: the pool, which is Bluesky, DEV Community, Hacker News, Lemmy, Reddit, Stack Exchange.
max_platforms1 to 12, default 6Cap on the platform list.
min_p0 to 1, default 0.5A row counts for a signal when its probability is at or above this.
top1 to 50, default 5How many examples per signal.
relevance, thresholdscore or filter; 0 to 1Also judge each row against the topic.
themestrueAdd stats.top_themes.
stricttrue (default) or falseWhat happens when nothing could be judged.
dry_runtrueSee Dry run.

The report always labels purchase_intent, question and complaint, so include and label are refused with 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:

FieldMeaning
stats.rowsRows over the legs that answered.
stats.volume_per_platformRows per platform, null for a platform whose leg failed.
stats.purchase_intent, stats.question, stats.complaintFor each: preset, threshold (= min_p), rows, judged, unjudged, count (rows at or above min_p) and share_of_judged (count / judged).
stats.top_themesThe largest clusters when themes=true, else null.
examples.<signal>The top rows by probability for each signal, with platform, p and the item.
labelsThe labels block: 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.

When AI is not enabled#

With the default strict=true the call is a free 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.