Documentation

One request contract, every report family.

Every report is a POST with the same shape: a date window, pre-aggregation filters, post-aggregation having — and a response that carries the components next to every ratio.

All paths are scoped to one project — /projects/{project_id}/… — key-authed and gated by the reporting_api flag. Dates are ISO (YYYY-MM-DD); the window grain is day, week or month.

The request

POST https://app.bementioned.ai/api/v1/projects/{project_id}/reports/brands

{
  "window": { "start": "2026-07-01", "end": "2026-07-31", "grain": "day" },
  "filters": [
    { "field": "country_code", "op": "in", "values": ["US"] }
  ],
  "having": [
    { "measure": "visibility", "op": "gte", "value": 0.25 }
  ],
  "order_by": "share_of_voice",
  "limit": 100,
  "offset": 0
}

filters[] is a pre-aggregation WHERE: it shapes which answers are counted before anything is summed. Fields and operators are closed sets:

model_channel_id · country_code · topic_id · prompt_id · tag_id · in · not_in · gt · gte · lt · lte

having[] is a post-aggregation HAVING over the computed measures — the same closed set order_by accepts:

visibility · share_of_voice · avg_position · sentiment · mention_count · responses_with_brand

One field is deliberately absent: there is no brand filter. The reported brand is selected after aggregation, so Share of Voice can never read 1.0 under a brand filter — the number stays honest by construction.

The response

Every ratio ships with its additive components — mention_count, responses_with_brand, and the window’s response_count — so you can re-aggregate correctly: sum the components, divide once. Deltas compare against the previous window of the same length.

{
  "start": "2026-07-01",
  "end": "2026-07-31",
  "response_count": 372,
  "all_brand_mention_count": 941,
  "gated_columns": [],
  "brands": [
    {
      "brand_id": 7, "name": "Your Brand", "role": "you",
      "visibility": 0.42, "share_of_voice": 0.19,
      "avg_position": 2.4, "sentiment": 0.61,
      "mention_count": 158, "responses_with_brand": 156,
      "delta": { "visibility": 0.03, "share_of_voice": -0.01,
                 "avg_position": 0.2, "sentiment": 0.0 }
    }
  ]
}

On plans without a premium column the value comes back null and the column id is listed in gated_columns — the response shape never changes with the plan.

Report families

The bodies vary only where the resource demands it: the sources reports add a grain and an optional gap block, the answers feed adds display filters. Everything else is the contract above.

EndpointWhat it returns
POST /reports/brandsThe top-brands table: Visibility, Share of Voice, position and sentiment per brand, with previous-window deltas.
POST /reports/timeseriesDaily, weekly or monthly series of the brand metrics, plus the own-domain citation series.
POST /reports/matrixThe brand × engine visibility pivot, each engine with its own denominator.
POST /reports/rankingsThe engine × answer-slot grid: which brand most often held each position.
POST /reports/promptsPer-prompt metrics for every tracked prompt, with a summary strip.
POST /reports/pairedBoth sides of a two-window comparison, for slope charts.
POST /reports/rank-weeklyEach brand’s ordinal rank, per ISO week.
POST /reports/slot-distributionHow often each brand held each answer slot — the distribution an average position collapses.
POST /reports/engine-compositionShare of Voice per brand within each engine, per-engine denominators.
POST /reports/coverage · /reports/coverage-disclosureWhat was collected — answer counts by status and engine — and how much of the market the tracked set covers.
POST /reports/absentThe prompts that ran and never named your brand.
POST /reports/prompt-leaders · /prompt-brands · /prompt-seriesWho leads each prompt and by how much, every brand’s share per prompt, and your own per-prompt weekly trend.
POST /reports/chatsThe paginated feed of collected answers.
GET /reports/chats/{chat_id}One answer, decomposed: messages, features, fan-outs, sources and mentions.
POST /reports/domains · /reports/urlsSource analytics at domain, host or URL grain; add the gap block for the gap variant.
POST /reports/source-brands · /reports/gap-fieldWhich brands the answers citing each source named, and the gap score decomposed into its two counts.
POST /reports/fanouts · /rollups · /coverage · /treeThe search queries the engines ran underneath the answers: the table, KPI rollups, coverage, and one prompt’s tree.
GET /overview · /channels/freshness · /reports/brand-perception · /reports/sources/{url_id}The overview strip, per-engine freshness watermarks, the latest perception themes, and one source’s crawled content.

CSV export

Five reports also export as CSV — GET endpoints whose query parameters mirror the POST bodies, gated by the csv_export flag, honouring the same filters and sort.

EndpointWhat it returns
GET /reports/brands/exportThe top-brands table.
GET /reports/prompts/exportThe per-prompt table.
GET /reports/domains/exportThe domains report — or the gap list, when the gap parameters are present.
GET /reports/urls/exportThe URLs report, with the same gap variant.
GET /reports/fanouts/exportFan-out queries, brands-in-searches or common phrases — one surface per request.