All five v2 report endpoints share one request and response shape:
Visibility,
Citations,
Sentiment,
Query Fanouts, and
Answers. Learn it once and every report
works the same way.
All v2 endpoints accept names or UUIDs anywhere a filter takes a value.
Get UUIDs from GET /v1/org/models, /v1/org/regions, /v1/org/personas,
/v1/org/assets, /v1/org/categories, and the per-category
…/topics, …/tags, …/prompts endpoints.
Common request fields
| Field | Type | Notes |
|---|
category_id | UUID (required) | The category to query. |
start_date / end_date | date (required) | YYYY-MM-DD, Eastern Time, inclusive on both ends. |
scope | owned · all | Restrict to your owned assets/domains, or rank everything. Defaults vary per report. |
group_by | string[] | Break results into rows by dimension (see Grouping). |
metrics | string[] | Which metrics to compute. Returned as named fields on each row. |
interval | day · week · month | Bucket size when grouping by date. Default day. |
filter | tree | and/or/not/leaf tree (see Filters). |
sort | { field, dir } | Order rows (where supported). |
limit | 1–50 | Rows per page. Default 10. |
cursor | string | Page token from info.next_cursor. |
Unlike the v1 reports (where end_date is exclusive), v2 end_date is
inclusive. To get June 9–15, send start_date: "2026-06-09",
end_date: "2026-06-15".
Grouping
group_by turns one aggregate row into one row per value. Each grouped field
is echoed back on the row:
// group_by: ["model"] → each row carries the model it's for
{ "asset": { "name": "Profound", "owned": true }, "model": { "id": "…", "name": "ChatGPT" }, "visibility_score": 0.52 }
- Group by
date (with interval) for a time series.
- Rows carry a
rank when grouped by a non-date dimension.
- Available dimensions differ per report; see each endpoint’s reference.
Metrics
Request the metrics you want; they come back as named fields on each row
(no positional arrays, no info.query lookup):
{ "rank": 1, "visibility_score": 0.48, "share_of_voice": 0.077, "average_position": 2.5 }
Scope and asset selection
scope: owned limits to assets/domains you own; all ranks across everything.
- Visibility only: pick the asset(s) with the separate
assets param: a
name (is), a list (in, which overrides scope), or { op, value }. A
selection returns all matches and ignores limit.
- Sentiment requires an
asset (sentiment is per-brand).
Filters
filter is a recursive tree, max depth 3, supported by all five reports:
{
"and": [
{ "or": [ { "field": "model", "op": "is", "value": "ChatGPT" },
{ "field": "model", "op": "is", "value": "Perplexity" } ] },
{ "not": { "field": "region", "op": "is", "value": "United States" } }
]
}
Node types: { "and": [ … ] }, { "or": [ … ] }, { "not": <node> }, and
leaves { "field", "op", "value" }.
Operators
| Operator | Meaning |
|---|
is / not_is | Exact match / negated |
in / not_in | Match any value in a list (non-empty) / negated |
contains / not_contains | Substring (case-sensitive) |
contains_case_insensitive / not_contains_case_insensitive | Substring (case-insensitive) |
matches | Regex (pattern ≥ 3 chars) |
exists | Has any value; only on tag / persona (wrap in not for “none”) |
value is a single value, or a list for in / not_in. Names or UUIDs;
contains / matches match on names.
Two filter layers
Fields fall into two layers. They combine with and; or/not can’t mix
layers (doing so returns 422).
Prompt layer (full tree, full operator set):
model, topic, region, persona, prompt, tag.
Entity / citation layer (top-level and leaves only, varies per report):
| Report | Entity-layer fields |
|---|
| Visibility | The entity (asset) uses the assets param, not filter. |
| Citations | domain (full ops, subdomain-aware), page (full ops), analysis_type (visibility·sentiment·factcheck·all), citation_category (owned·competition·social·earned_media·earned_institutions·pr_wire·other·custom) |
| Sentiment | theme / claim: is/in, single value, name or id |
| Query Fanouts | analysis_type (visibility·sentiment·factcheck·all), is/in |
| Answers | analysis_type is prompt-level (visibility·sentiment·factcheck; is/in/not_in; omit = all). domain/page are top-level and leaves: is one value or in a list (exact cited-URL match) |
For citations, filter domain in the domains report and page when you
group_by: ["page"]; each filters its own report’s entity.
Sorting
Where supported, sort is { "field": "<metric>", "dir": "asc" | "desc" }.
The field must be a requested, sortable metric (or date when grouped by
date). Citations has no sort: it’s always ranked most-cited first.
Responses return limit rows plus info.next_cursor. Pass that token back as
cursor to get the next page; next_cursor is null on the last page.
Streaming
Every endpoint has a /stream variant (Server-Sent Events): a summary
event (the info block), then one result event per row. limit/cursor
are ignored; it returns everything by default. Pass max_results to cap.
Response shape
Every report returns { info, data }:
{
"info": {
"total_results": 8427,
"count": 10,
"next_cursor": "…",
"models": ["ChatGPT", "Google Gemini", "..."],
"start_date": "2026-06-09",
"end_date": "2026-06-15",
"filter": null
},
"data": [
{ "rank": 1, "visibility_score": 0.48 }
]
}
info echoes the resolved query (models in scope, the applied filter, dates,
pagination); data is the rows, with metrics as named fields and any
group_by dimensions attached.