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.
POST https://api.scrunchai.com/v2/query/{brand_id}
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.
curl -X POST "https://api.scrunchai.com/v2/query/$SCRUNCH_BRAND_ID" \
  -H "Authorization: Bearer $SCRUNCH_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d @query.json

Request body

FieldTypeRequiredDescription
fieldsstring[]YesDimensions and metrics to return. 1–32 entries. See Fields reference.
start_datestring (YYYY-MM-DD)NoInclusive start. Defaults to 30 days ago. Required when comparison is set.
end_datestring (YYYY-MM-DD)NoInclusive end. Defaults to today. Required when comparison is set.
filtersDimensionFilter[]NoPre-aggregation WHERE filters on dimensions. Up to 25 entries.
havingHavingFilter[]NoPost-aggregation HAVING filters on metrics. Up to 10 entries.
comparisonComparisonNoPeriod-over-period comparison configuration.
limitintegerNoRow cap. Default 50000, maximum 90000.
offsetintegerNoRow offset for pagination. Default 0.

DimensionFilter

{ "field": "ai_platform", "values": ["ChatGPT"], "negate": false }
FieldTypeDescription
fieldstringA filterable dimension. prompt is not filterable.
valuesarrayOne or more values to match. Up to 1000 entries. Values are coerced to the dimension’s type — booleans accept true/false, 1/0, "true"/"false", "yes"/"no"; integer dimensions accept numeric strings. Mismatched types return HTTP 422.
negatebooleanWhen true, excludes rows that match. Default false.

HavingFilter

{ "metric": "responses", "operator": "gte", "value": 10 }
FieldTypeDescription
metricstringA metric in fields. Referencing a metric not in fields is an error.
operatorenumOne of gt, gte, lt, lte, eq, neq.
valuenumberThe threshold. inf and nan are rejected.

Comparison

{ "mode": "prior_period" }
FieldTypeDescription
modeenumprior_period shifts back by the window length. prior_year shifts back by one calendar year (Feb 29 falls back to Feb 28 in non-leap years).
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.
{
  "columns": [
    { "name": "date_week", "kind": "dimension", "dtype": "date" },
    { "name": "brand_presence_percentage", "kind": "metric", "dtype": "float" }
  ],
  "rows": [
    ["2026-W14", 0.421],
    ["2026-W15", 0.478]
  ],
  "pagination": { "limit": 50000, "offset": 0 },
  "compare_status": null
}
FieldTypeDescription
columnsarrayColumn metadata in the same order as rows. kind is dimension or metric. dtype is one of string, int, float, bool, date.
rowsarrayEach row has one cell per column.
pagination.limitintegerThe row cap clients should use for end-of-page detection. Echoes limit for non-comparison queries and for successful comparison queries (each period is capped at limit / 2 rows, and the merged result never exceeds limit). When compare_status is "current_only", returns limit / 2 — the cap that was actually applied to the surviving current series.
pagination.offsetintegerEchoes the requested offset.
compare_status"current_only" | nullSet to "current_only" when comparison was requested but the prior-period query failed; the response then contains only the current series. null otherwise.
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.

Examples

Multi-metric weekly trend with a platform filter

curl -X POST "https://api.scrunchai.com/v2/query/$SCRUNCH_BRAND_ID" \
  -H "Authorization: Bearer $SCRUNCH_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "start_date": "2026-04-01",
    "end_date":   "2026-04-30",
    "fields": ["date_week", "brand_presence_percentage", "brand_unique_prompts"],
    "filters": [
      { "field": "ai_platform", "values": ["ChatGPT"] }
    ]
  }'

Threshold the result with HAVING

Return only weeks that received at least 10 responses.
curl -X POST "https://api.scrunchai.com/v2/query/$SCRUNCH_BRAND_ID" \
  -H "Authorization: Bearer $SCRUNCH_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "start_date": "2026-04-01",
    "end_date":   "2026-04-30",
    "fields": ["date_week", "responses", "brand_presence_percentage"],
    "having": [
      { "metric": "responses", "operator": "gte", "value": 10 }
    ]
  }'

Period-over-period comparison

curl -X POST "https://api.scrunchai.com/v2/query/$SCRUNCH_BRAND_ID" \
  -H "Authorization: Bearer $SCRUNCH_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "start_date": "2026-04-01",
    "end_date":   "2026-04-30",
    "fields": ["date_week", "brand_presence_percentage"],
    "comparison": { "mode": "prior_period" }
  }'
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

{
  "fields": ["prompt_topic", "responses"],
  "filters": [
    { "field": "prompt_topic", "values": ["Pricing", "Support"], "negate": true }
  ]
}

Limits

LimitValue
Fields per request32
Filters per request25
HAVING clauses per request10
Values per filter1000
Rows per response (limit)90000
Default rows per response50000
Server-side execution time30 seconds
Requests that exceed these limits are rejected with HTTP 422.

Errors

StatusCause
401Missing or invalid API key.
403API key lacks the query scope or access to the brand.
400The field combination cannot be built into a single query — for example, citation metrics combined with raw-path-only fields, or a share-of-voice metric with an unsupported breakdown. Also returned for an unknown citation_segment_id filter value.
404Brand not found.
422Request body fails validation (for example, a non-filterable dimension in filters, a HAVING metric not in fields, start_date after end_date, a value that cannot be coerced to the dimension’s type, a HAVING value of inf or nan, or a request that exceeds the size limits).
500Server error. When triggered by the prior-period query in a comparison request, the response succeeds with compare_status: "current_only" instead.

Query API overview

Field reference shared with the GET endpoint.

Query API quickstart

First request walkthrough.