Skip to main content

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.
The structured endpoint and the 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.
For straightforward dimension-and-metric pulls, the GET query endpoint remains the simpler choice.

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 on tag 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: dateYYYY-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_monthYYYY-MM, date_quarterYYYY-Q#, date_yearYYYY.

Totals and remainder

Set include_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 a rollup 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

The response prepends a 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. Without match: "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:
Combine "__none__" with real values in the same filter — the filter matches either the untagged prompts or the named tags:
Set 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


Query API overview

Field reference shared with the GET endpoint.

Query API quickstart

First request walkthrough.