# Brand mentions report

`GET https://api.onekey.dev/v1/reports/brand-mentions?brand=...` searches for a brand on several platforms, merges the answers by rank, and counts. You get how many mentions were found and where, who posted the most engaging ones, the top mentions, and, if you ask for it, counts of sentiment, complaints, questions and themes. See [Reports](https://onekey.dev/docs/reports.md) for the shape and rules every report shares.

```bash
curl "https://api.onekey.dev/v1/reports/brand-mentions?brand=rust&platforms=hackernews,devto&top=3" \
  -H "x-api-key: YOUR_API_KEY"
```

## Inputs

| Parameter | Values | What it does |
| --- | --- | --- |
| `brand` | text, required, up to 200 characters | The brand or product name to search for. |
| `platforms` | comma list | Search exactly these. Each needs a search endpoint. Default: the [pool](https://onekey.dev/docs/reports.md#default-pools-today), which is Bluesky, DEV Community, Hacker News, Lemmy, Mastodon, Reddit, Stack Exchange, TikTok, Twitch, X (Twitter), YouTube. |
| `max_platforms` | 1 to 12, default 6 | Cap on the platform list. |
| `top` | 1 to 50, default 10 | How many `top_authors` and `top_mentions` to return. |
| `include=labels`, `label=` | presets | Judge every row. Default presets `sentiment`, `complaint`, `question`. See [Labels](https://onekey.dev/docs/labels.md). AI. |
| `relevance`, `threshold` | `score` or `filter`; 0 to 1 | Judge each row against the brand name. AI. |
| `themes` | `true` | Cluster the rows and name the clusters. AI. |
| `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). |

An unknown parameter is refused with [`INVALID_PARAMS`](https://onekey.dev/docs/errors.md#invalid-params).

## The response

Without any AI parameter, the report has these fields on top of the [shared shape](https://onekey.dev/docs/reports.md#the-shape-every-report-shares):

| Field | Meaning |
| --- | --- |
| `stats.mentions` | Items over the legs that succeeded. |
| `stats.volume_per_platform` | Items per platform. `null` for a platform whose leg failed, never 0. |
| `top_authors` | Authors per platform, ranked (below). Each has `platform`, `author`, `mentions`, `engagement_rate`, `interactions` and `ranked_by`. |
| `top_mentions` | The first `top` entries of the rank-fused list, each with `score`, `platform`, `platforms` and the `item` itself. |
| `fuse` | `{method: "rrf", k: 60}`: [reciprocal rank fusion](https://onekey.dev/docs/search-multi.md#rank-fusion-fuse-rrf). |

### How top authors are ranked

`ranked_by` tells you which basis was used, and the tiers come in this order: authors whose items carry a divisor are ranked by `computed.engagement_rate` (the [published formula](https://onekey.dev/docs/computed.md)); then by raw interactions (likes plus comments plus shares); then by mention count.

**Expect `interactions`.** `engagement_rate` needs views or a follower count on the item, and most search rows carry neither. On those rows `engagement_rate` is `null` and the report falls back to raw interactions, saying so in `ranked_by`. That is a different scale: interactions are counts, not a rate, and are not comparable across platforms with different audience sizes.

## The AI part

> [!NOTE] **Not enabled yet.** This part is an AI step: it ships when a production model is connected and is switched on per account; the numbers shown come from the pre-release test provider and will change. It is metered like the other [enrichment](https://onekey.dev/docs/ai-pricing.md) steps. The rest of this page needs no AI.

Ask with `include=labels`, `themes=true` or `relevance=` and the report adds counts:

| Field | Meaning |
| --- | --- |
| `stats.sentiment` | Counts by level: `positive` (levels 4 and 5), `neutral` (3), `negative` (1 and 2), plus `unjudged` and `judged`. `null` unless labels were requested. |
| `stats.complaints`, `stats.questions` | Rows at or above a probability of 0.5 for that preset, with `rows`, `judged`, `unjudged`, `count` and `share_of_judged`. |
| `stats.top_themes` | The largest clusters of the `themes` block. |
| `labels`, `themes`, `relevance` | The same blocks as on [multi-platform search](https://onekey.dev/docs/search-multi.md), with their `methodology_version`. |

Platforms whose search endpoint does not accept labels are counted in the volume but never judged (`labels.unsupported` lists them).

## A recorded call

Recorded on the dev API with the pre-release test provider, heavily shortened:

```bash
curl "https://api.onekey.dev/v1/reports/brand-mentions?brand=rust&platforms=hackernews,devto&include=labels&themes=true&top=3" \
  -H "x-api-key: YOUR_API_KEY"
```

```json
{
  "success": true,
  "data": {
    "report": "brand_mentions",
    "methodology_version": "brand_mentions/1",
    "inputs": {
      "brand": "rust", "platforms": ["hackernews", "devto"], "selection": "explicit", "top": 3,
      "subqueries": { "hackernews": "rust", "devto": "rust" }, "adapted": {}, "skipped": {},
      "labels": ["complaint", "question", "sentiment"], "themes": true, "relevance": null
    },
    "legs": ["..."],
    "coverage": { "answered": ["hackernews", "devto"], "failed": [], "missing_fields": {} },
    "stats": {
      "mentions": 20,
      "volume_per_platform": { "hackernews": 10, "devto": 10 },
      "sentiment": { "positive": 0, "neutral": 20, "negative": 0, "unjudged": 0, "judged": 20 },
      "complaints": { "preset": "complaint", "threshold": 0.5, "rows": 20, "judged": 20, "unjudged": 0, "count": 1, "share_of_judged": 0.05 },
      "questions": { "preset": "question", "threshold": 0.5, "rows": 20, "judged": 20, "unjudged": 0, "count": 3, "share_of_judged": 0.15 },
      "top_themes": [ { "theme": "Rust Switching Bot", "size": 5, "terms": ["rust", "switching", "bot", "boxr", "card"], "sample": "Why Discord is switching from Go to Rust" } ]
    },
    "top_authors": [
      { "platform": "hackernews", "author": "formalsystem", "mentions": 2, "engagement_rate": null, "interactions": 3136, "ranked_by": "interactions" }
    ],
    "top_mentions": [
      {
        "platform": "hackernews", "platforms": ["hackernews"], "score": 0.016393,
        "item": {
          "content": { "id": "22238335", "title": "Why Discord is switching from Go to Rust" },
          "author": { "username": "Sikul" },
          "engagement": { "likes": 1582, "comments": 642 },
          "computed": { "engagement_rate": null, "language": "en", "labels": { "complaint": { "p": 0.05 }, "question": { "p": 0.7 }, "sentiment": { "level": 3, "score_0_1": 0.5, "confidence": 0.35 } } }
        }
      }
    ],
    "fuse": { "method": "rrf", "k": 60 },
    "labels": { "status": "complete", "presets": ["complaint", "question", "sentiment"], "rows": 20, "labelled": 20, "extra_credits": 27.915, "methodology_version": "fake/1" },
    "themes": { "status": "complete", "rows": 20, "named": true, "extra_credits": 6.211, "methodology_version": "fake/1" },
    "ai_methodology_version": "fake/1",
    "credits": { "legs": 5.0, "ai": 34.126, "total": 39.126 }
  },
  "metadata": { "recipe": "brand_mentions", "report": "brand_mentions", "methodology_version": "brand_mentions/1", "credits_charged": 39.126, "extra_credits": 34.126, "pipeline": ["..."] }
}
```

How to read it: all 20 rows are neutral because the test provider's sentiment on plain titles is level 3 by construction, and `engagement_rate` is `null` on every author, so `ranked_by` is `interactions`. The counts show that the pipeline works end to end; they are not findings about Rust.

## Costs

Measured on the pre-release test provider (synthetic usage, so it shows how the charge scales, not what production judgments will cost): 5 credits for the legs (Hacker News was a cache hit from an earlier call), 27.915 for judging 20 rows with 3 presets (1.40 per row), 6.211 for naming 7 clusters, 39.126 in all. Fewer presets are cheaper. The legs alone, without any AI parameter, cost only the search endpoints' prices. See [AI pricing](https://onekey.dev/docs/ai-pricing.md) for how metering works.

## When AI is not enabled

Without `include=labels`, `stats.sentiment` is `null`, and the rest of the report works. With labels requested and 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` you get `stats.sentiment` as `{positive: 0, neutral: 0, negative: 0, unjudged: 20, judged: 0}` and `labels.reason: "provider_off"`, at the legs' price.

## Limits

- Each platform's search decides what counts as a mention. The report counts what the search returned, the first page of each, not everything ever written.
- A tag-style search receives one keyword. `inputs.adapted` shows it.
- `top_authors` falls back to raw interactions on most searches, as above.
- AI-derived counts are plumbing checks until a production method exists.
- 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).
