Skip to content

Cross-tabulate two dimensions across a population

POST
/v1/discovery/crosstab
curl --request POST \
--url https://dev-api.infiniteaudience.ai/v1/discovery/crosstab \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "segment_id": "seg_1a2b3c", "row_field": "state", "col_field": "gender", "row_label": "State", "col_label": "Gender", "max_rows": 10 }'

Returns a pivot table of counts for two categorical dimensions (e.g. state × gender). Requires ‘discovery’ scope. Provide exactly one of: filters, filter_groups, segment_id, audience_id, or segment_ids + set_logic to define the population (same input shapes as /v1/discovery/count). Sending more than one returns 400 AMBIGUOUS_INPUT; sending none returns 400 NO_INPUT_SPECIFIED. A 400 SAME_FIELD is returned if row_field equals col_field. If you pass a segment, audience, or filter groups, full group/combinator structure is preserved — each segment’s own AND/OR groups are evaluated intact, multiple composed segments are combined by set_logic, and an audience’s excluded segments are always subtracted (AND NOT), regardless of set_logic.

Media typeapplication/json
object
filters

Flat filter array defining the population.

Array<object>

A single filter condition for audience discovery or job execution

object
field
required

The canonical attribute name (e.g. ‘age’, ‘state’)

string
op
required

Comparison operator

string
Allowed values: = != < <= > >= IN NOT IN BETWEEN LIKE ARRAY_CONTAINS ARRAY_CONTAINS_ANY IS NULL IS NOT NULL
value
One of:
string
filter_groups

Compound boolean filter groups (alternative to filters).

Array<object>

A group of Filter conditions. All filters within a single group are always ANDed together — there is no per-group intra-operator. The combinator attribute is an inter-group operator: it controls how this group is joined to the immediately preceding group in the array. The combinator on the first group (index 0) is always ignored.

Example — (city = "NYC" AND state = "NY") OR (state = "CA"):

  { "id": "g1", "filters": [{"field":"city","op":"=","value":"NYC"},
                            {"field":"state","op":"=","value":"NY"}],
    "combinator": "AND" },
  { "id": "g2", "filters": [{"field":"state","op":"=","value":"CA"}],
    "combinator": "OR" }
] ```
Group g1's `combinator` is irrelevant (it is the first group). Group g2's `combinator: "OR"` means the result is `(g1) OR (g2)`.
object
id
required

Stable identifier (UUID or NanoID). Used to track groups across edits.

string
filters
required

One or more filter conditions. All conditions in this array are combined with AND. Provide at least one filter per group.

Array<object>

A single filter condition for audience discovery or job execution

object
field
required

The canonical attribute name (e.g. ‘age’, ‘state’)

string
op
required

Comparison operator

string
Allowed values: = != < <= > >= IN NOT IN BETWEEN LIKE ARRAY_CONTAINS ARRAY_CONTAINS_ANY IS NULL IS NOT NULL
value
One of:
string
combinator
required

Inter-group operator. Joins this group to the previous group in the array. AND narrows the result; OR broadens it. Ignored on the first group (index 0) — that group has no predecessor to join.

string
Allowed values: AND OR
segment_id

ID of a saved segment defining the population.

string
audience_id

ID of a saved audience defining the population.

string
segment_ids

IDs of multiple segments to combine (requires set_logic).

Array<string>
set_logic

How to combine segment_ids: union = match ANY segment (OR); intersection = match EVERY segment (AND).

string
Allowed values: union intersection
excluded_segment_ids

Ad-hoc “A minus B” preview — segments to subtract from the segment_ids composition, without persisting an audience. Only meaningful alongside segment_ids.

Array<string>
row_field
required

Attribute for the row dimension (e.g. “state”).

string
>= 1 characters <= 64 characters
col_field
required

Attribute for the column dimension (e.g. “gender”). Must differ from row_field.

string
>= 1 characters <= 64 characters
row_label

Human-readable row label (defaults to row_field).

string
<= 80 characters
col_label

Human-readable column label (defaults to col_field).

string
<= 80 characters
max_rows

Max distinct row values, ordered by total count desc.

integer
default: 20 >= 1 <= 50
max_cols

Max distinct column values, ordered by total count desc.

integer
default: 10 >= 1 <= 20
Example
{
"segment_id": "seg_1a2b3c",
"row_field": "state",
"col_field": "gender",
"row_label": "State",
"col_label": "Gender",
"max_rows": 10
}

Pivot table result.

Media typeapplication/json
object
row_field
required
string
col_field
required
string
row_label
required

Echoes row_label (defaults to row_field).

string
col_label
required

Echoes col_label (defaults to col_field).

string
rows
required

Distinct row values, ordered by total count desc.

Array<string>
columns
required

Distinct column values, ordered by total count desc.

Array<string>
cells
required

Cells[rowValue][colValue] = record count.

object
key
additional properties
object
key
additional properties
integer
total_count
required

Total records matching the population input.

integer
Example
{
"row_field": "state",
"col_field": "gender",
"row_label": "State",
"col_label": "Gender",
"rows": [
"CA",
"TX"
],
"columns": [
"F",
"M"
],
"cells": {
"CA": {
"F": 610230,
"M": 588112
},
"TX": {
"F": 402881,
"M": 399124
}
},
"total_count": 2000347
}

Invalid request — malformed body, missing required attribute, or failed validation. See error and message for details.

Media typeapplication/json
object
error
required

Stable machine-readable error code (e.g. INVALID_STATUS_TRANSITION, BILLING_INSUFFICIENT_BALANCE). Always present.

string
message

Human-readable explanation of the error.

string
code

Alternate machine-readable code — present on some endpoints as an alias for error for backward compatibility.

string
request_id

Opaque support/debug identifier when available.

string
key
additional properties
any
Examples
Examplevalidation_error
{
"error": "Bad Request",
"code": "MISSING_SEGMENTS",
"message": "segment_ids is required for filter audiences."
}

Missing or invalid Bearer token. Obtain one via POST /v1/auth/token. When a token was supplied but rejected, code distinguishes TOKEN_EXPIRED (the token’s lifetime has passed — request a new one via POST /v1/auth/token and retry) from TOKEN_INVALID (malformed or revoked — re-authenticate).

Media typeapplication/json
object
error
required

Stable machine-readable error code (e.g. INVALID_STATUS_TRANSITION, BILLING_INSUFFICIENT_BALANCE). Always present.

string
message

Human-readable explanation of the error.

string
code

Alternate machine-readable code — present on some endpoints as an alias for error for backward compatibility.

string
request_id

Opaque support/debug identifier when available.

string
key
additional properties
any
Examples
{
"error": "Unauthorized: Missing or invalid Authorization header"
}

Token is valid but lacks the required scope for this endpoint. Check the endpoint description for the required scope (discovery, purchase, or account).

Media typeapplication/json
object
error
required

Stable machine-readable error code (e.g. INVALID_STATUS_TRANSITION, BILLING_INSUFFICIENT_BALANCE). Always present.

string
message

Human-readable explanation of the error.

string
code

Alternate machine-readable code — present on some endpoints as an alias for error for backward compatibility.

string
request_id

Opaque support/debug identifier when available.

string
key
additional properties
any
Examples
Examplemissing_scope
{
"error": "SCOPE_REQUIRED",
"message": "This endpoint requires the purchase scope."
}

Request throttled. Two distinct conditions return 429: Rate limit — too many requests per minute for your tier (error: rate_limit_exceeded); retry after retry_after_seconds. Compute ceiling — org-level BigQuery scan budget exhausted (error: compute_limit_exceeded); resets hourly or on plan upgrade.

Media typeapplication/json
object
error
required

Stable machine-readable error code (e.g. INVALID_STATUS_TRANSITION, BILLING_INSUFFICIENT_BALANCE). Always present.

string
message

Human-readable explanation of the error.

string
code

Alternate machine-readable code — present on some endpoints as an alias for error for backward compatibility.

string
request_id

Opaque support/debug identifier when available.

string
key
additional properties
any
Examples
{
"error": "rate_limit_exceeded",
"message": "Rate limit of 60 requests/minute exceeded for tier 'starter'.",
"retry_after_seconds": 45
}

The underlying BigQuery query failed, or (AGGREGATION_FLOOR_NOT_MET) the result set is too small to return while preserving individual-level privacy. message is always a friendly, client-safe description — the raw BigQuery error is never returned.

Media typeapplication/json
object
error
required

Stable machine-readable error code (e.g. INVALID_STATUS_TRANSITION, BILLING_INSUFFICIENT_BALANCE). Always present.

string
message

Human-readable explanation of the error.

string
code

Alternate machine-readable code — present on some endpoints as an alias for error for backward compatibility.

string
request_id

Opaque support/debug identifier when available.

string
key
additional properties
any
Examples
{
"error": "Bad Gateway",
"code": "AGGREGATION_FLOOR_NOT_MET",
"message": "For privacy reasons, a minimum of 50 records is required to view this information."
}