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.
| Endpoint | What it returns |
|---|---|
POST /reports/brands | The top-brands table: Visibility, Share of Voice, position and sentiment per brand, with previous-window deltas. |
POST /reports/timeseries | Daily, weekly or monthly series of the brand metrics, plus the own-domain citation series. |
POST /reports/matrix | The brand × engine visibility pivot, each engine with its own denominator. |
POST /reports/rankings | The engine × answer-slot grid: which brand most often held each position. |
POST /reports/prompts | Per-prompt metrics for every tracked prompt, with a summary strip. |
POST /reports/paired | Both sides of a two-window comparison, for slope charts. |
POST /reports/rank-weekly | Each brand’s ordinal rank, per ISO week. |
POST /reports/slot-distribution | How often each brand held each answer slot — the distribution an average position collapses. |
POST /reports/engine-composition | Share of Voice per brand within each engine, per-engine denominators. |
POST /reports/coverage · /reports/coverage-disclosure | What was collected — answer counts by status and engine — and how much of the market the tracked set covers. |
POST /reports/absent | The prompts that ran and never named your brand. |
POST /reports/prompt-leaders · /prompt-brands · /prompt-series | Who leads each prompt and by how much, every brand’s share per prompt, and your own per-prompt weekly trend. |
POST /reports/chats | The paginated feed of collected answers. |
GET /reports/chats/{chat_id} | One answer, decomposed: messages, features, fan-outs, sources and mentions. |
POST /reports/domains · /reports/urls | Source analytics at domain, host or URL grain; add the gap block for the gap variant. |
POST /reports/source-brands · /reports/gap-field | Which brands the answers citing each source named, and the gap score decomposed into its two counts. |
POST /reports/fanouts · /rollups · /coverage · /tree | The 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.
| Endpoint | What it returns |
|---|---|
GET /reports/brands/export | The top-brands table. |
GET /reports/prompts/export | The per-prompt table. |
GET /reports/domains/export | The domains report — or the gap list, when the gap parameters are present. |
GET /reports/urls/export | The URLs report, with the same gap variant. |
GET /reports/fanouts/export | Fan-out queries, brands-in-searches or common phrases — one surface per request. |