Overview
The structured query endpoint accepts a JSON request body describing the dimensions, metrics, filters, and comparisons you want, and returns a columnar result. Use it when you need richer query shapes than the GET query endpoint supports — for example, post-aggregation thresholds, negated filters, or period-over-period comparison in a single round trip.GET endpoint share the same dimensions, metrics, and brand scoping. They differ in expressivity and request shape: the structured endpoint takes a JSON body and adds having, negate, and comparison.
When to use
Use the structured endpoint when you need to:- Filter on metric thresholds after aggregation (for example, only weeks with at least 10 responses).
- Exclude a set of values rather than include them (
negate: true). - Pull a current-period result and a prior-period result in one request, aligned for charting.
- Submit complex filter combinations that are awkward to encode in URL parameters.
Authentication
Authenticate with a Bearer API key. The key must include the Query scope and have access to the target brand. See Provision an API Key.Request body
DimensionFilter
Match modes
By default, multi-value filters combine with OR — a filter ontag with ["A", "B"] returns prompts tagged A or B. This widens the result set as you add values.
Set match: "all" on tag or prompt_topic to combine with AND instead — the filter then returns only prompts tagged A and B, narrowing the result set. Use this when a prompt can carry multiple tags or topics and you want to isolate the intersection (for example, product-level reporting that requires two tags at once).
match only applies to tag and prompt_topic, which map each prompt to a set of values. Scalar dimensions like ai_platform or country reject match: "all" with HTTP 422 — a row has one platform, so AND across values would match nothing. Omit match (or send "any") on scalar dimensions.
Filtering by citation segment
citation_segment_id is filter-only: pass segment ids in values to scope citation metrics to specific segments. It cannot appear in fields — break results down by segment name with the citation_segment dimension instead. Segment ids are brand-scoped; an id that does not belong to the brand returns HTTP 400.
Each segment filters by its configured match mode. Prefix segments (the default) match their entries and any subpath. Exact segments match only the listed pages, ignoring letter case, www., ports, query strings, and trailing slashes.
Managed Agent Pages segments resolve to the pages their AXP site is serving right now, so the filter tracks deployments automatically. Pass multiple ids to combine sites — for example, a www site and a docs subdomain that the domain filter cannot separate. A managed segment whose site currently serves no pages matches zero rows.
HavingFilter
Comparison
When
comparison is set, start_date and end_date are required and the response includes a synthetic period dimension with values current and prior. Each period is capped at limit / 2 rows so the merged result stays within limit. Prior-period dates are aligned to current-period bucket labels so the two series overlay on a chart.
Response
The response is columnar.
Date dimensions are formatted as labels:
date → YYYY-MM-DD, date_week → ISO YYYY-Www (uses ISO week-numbering year, so dates near the year boundary group with their ISO week — for example, 2024-12-30 is 2025-W01), date_month → YYYY-MM, date_quarter → YYYY-Q#, date_year → YYYY.
Totals and remainder
Setinclude_totals: true to receive a totals object alongside rows. The server computes the grand total in SQL over the full result set, so it does not drift when limit truncates the rows or when a top-N request excludes long-tail slices.
Use
totals.values to render an accurate percentage-of-total on a page that shows only a top-N slice, and totals.remainder to display a real “Other” slice in a donut or pie without querying the tail separately.
include_totals is rejected in combination with comparison: the response interleaves two periods, so a single grand total describes neither. Request the two periods separately.
Rolling up metrics client-side
Every metric column carries arollup mode that tells you how to collapse its per-row values into one window-level number — the arithmetic the server would perform if you re-queried without the axis you are flattening. Use it when you fold a time-bucketed or breakdown-sliced response down to a single value in the client.
Averaging or summing without following the declared
rollup gives measurably wrong window-level numbers on rate and average metrics — an unweighted mean of per-day rates weights a 1-response day the same as a 4,000-response day. When you only need the window aggregate and do not care about the per-row values, set include_totals: true instead and read totals.values.
Examples
Multi-metric weekly trend with a platform filter
Threshold the result with HAVING
Return only weeks that received at least 10 responses.Period-over-period comparison
period column. Prior dates are mapped to current-period labels so the series can be plotted on a single x-axis.
Exclude specific prompts
Match all tags (AND) instead of any (OR)
Return only prompts carrying both tags. Withoutmatch: "all", this filter returns prompts carrying either tag.
Filter for prompts with no tag or topic
Pass the reserved token"__none__" in values on a tag or prompt_topic filter to match prompts that carry no tag or no topic. Use it to isolate the untagged tail of your prompt set — for example, to audit coverage gaps or to compute presence on the unclassified slice.
Only tag and prompt_topic accept "__none__". Sending it on any other dimension returns HTTP 422.
Return only untagged prompts:
"__none__" with real values in the same filter — the filter matches either the untagged prompts or the named tags:
negate: true to invert the whole filter — for example, prompts that carry at least one tag:
"__none__" cannot combine with match: "all": the request returns HTTP 422. A prompt with no tag has an empty tag set, so requiring it to contain “every” value including "__none__" is contradictory. Keep match as the default "any" (OR) when the filter uses "__none__".
A tag literally named __none__ becomes unreachable by equality — the token always resolves to the absence match. Rename such a tag if you need to filter for it directly.
Top slices with a grand total and “other” remainder
Request the top platforms for a metric alongside the full-window total.totals.values gives the accurate window-level number, and totals.remainder accounts for everything past the returned rows.
Limits
Requests that exceed these limits are rejected with HTTP
422.
Errors
Related
Query API overview
Field reference shared with the
GET endpoint.Query API quickstart
First request walkthrough.