Audiences
Build, list, read and delete custom audiences. Audience creation is asynchronous: the create call returns immediately with a new id, and you poll the get endpoint until the audience reaches its completed lifecycle status. To run a completed audience on a recurring data window - rebuilt and optionally re-exported every cycle - see Schedules.
All audience endpoints are authenticated and JSON-only. Send Authorization: Bearer <token> and Accept: application/json on every call (plus Content-Type: application/json on the POST create). Reads use the read rate bucket (120
requests/min per caller); the create and delete use the write bucket (30
requests/min per caller).
Create Audience
POST /api/v2/analyses/audiences/create
Creates a custom audience for the authenticated user’s company from one or two
datasets. Two datasets are combined with an operator. An optional crossvisitation block narrows the
audience to people who visited selected points of interest; an optional
crosspurchase block runs the built audience against the affinity purchase
data. The audience is queued
asynchronously; poll
GET /api/v2/analyses/audiences/{id} for its
status and results_count.
Auth: bearer token + Accept: application/json + Content-Type: application/json. Rate limit: write bucket (30 requests/min). Optional:
Idempotency-Key header (see below).
The build counts toward your company’s monthly data-scan limit (see
Usage). When the limit has been reached and is enforced, the
call is refused with a 422 whose message names the limit and the date it
resets, before anything is created or queued.
Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Audience name (max 255 chars). |
operator | string | Conditional | How to combine two datasets: AND, OR, or NOTIN. Required when datasets holds two blocks; rejected (422) when it holds one. Fetch the accepted values from GET /api/v2/analyses/reference/common/operators. |
project_id | integer | No | A project id owned by your company to file the audience under. |
datasets | object[] | Yes | One or two dataset blocks (array of size 1 or 2). |
crossvisitation | object | No | Optional POI crossvisitation analysis. Permission-gated - see below. |
crosspurchase | object | No | Optional cross purchase analysis run on the built audience. Permission-gated - see below. |
analyses | object | No | The frequency analyses to run as the audience builds - frequency (with its frequency_day_part sub-option), apps_frequency and web_frequency, all booleans - the same toggles as the Audience Manager and what the activation preview depends on. Permission-gated, each tied to its dataset type - see below. |
datastreams | object[] | No | Data stream visualizations to generate as the audience builds - see below. |
An audience holds one or two datasets. A single dataset takes no operator; two
datasets require an operator (AND, OR, or NOTIN). Each dataset is validated
and company-scoped identically. A second dataset that is present but missing its
type is rejected with 422.
Each datasets[i] block always carries a type, and (except for Cohorts and
Demographics) a start_date and end_date. The remaining fields depend on the type. The type
is one of the dataset types your company is entitled to, fetchable from
GET /api/v2/analyses/reference/common/dataset-types.
Common to each datasets[i]:
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | The dataset type. Fetch the types your company is entitled to from GET /api/v2/analyses/reference/common/dataset-types - a type not enabled for your account is rejected with 422. |
start_date | string (Y-m-d) | Yes for all except Cohorts and Demographics | Window start date. |
end_date | string (Y-m-d) | Yes for all except Cohorts and Demographics | Window end date. |
signal_providers | string[] | Yes (all types except Cohorts and Demographics) | BID values identifying the signal source: a list of distinct strings, at most 200. Fetch valid values for the dataset type from GET /api/v2/analyses/reference/common/signal-providers. A provider already used on one of your organization’s audiences of the same dataset type is also accepted, as long as it is still a signal provider. Any other value is rejected with 422 naming it. |
languages | string[] | No | Language codes. Fetch valid values from GET /api/v2/analyses/reference/common/languages. |
location | object | countries required (all types except Cohorts, AffinityTransactions and ProfileAttributes) | { countries[], states[], cities[], dmas[], zipcodes[] } (string arrays). countries come from GET .../reference/common/countries; states cascade via GET .../reference/common/states; cities cascade via GET .../reference/common/cities; dmas (POI and Origin only) come from GET .../reference/common/dmas. |
Every key in a dataset block must be one the block’s type defines (the common
keys above plus the type’s own fields below). Any other key - a misspelling, a
key from another type, or a filter the type does not have - is rejected with
422 naming the key, never silently ignored. The same applies inside
location.
WebDomain fields:
start_date must fall within the last 45 days of available data: web data
lands with a ~48-hour delay, so the window ends two days ago (UTC). Older
visitation data is archived and unreadable - an earlier start_date is
rejected with 422. The window rolls forward one day at UTC midnight.
| Field | Type | Required | Description |
|---|---|---|---|
iab_category_codes | integer[] | One of iab_category_codes / web_domains | IAB category ids. Fetch valid values from GET .../reference/web/iab-categories (and .../web/iab-subcategories). |
web_domains | string[] | One of iab_category_codes / web_domains | Web domains to target. Domains must exist in the catalog - fetch valid values from GET .../reference/web/domains; a domain outside the catalog is rejected with 422. |
web_ref_domains | string[] | No | Referrer domains to target. Fetch valid values from GET .../reference/web/ref-domains. |
device_types | string[] | No | Web device type names. Fetch valid values from GET .../reference/web/device-types. |
device_makes | string[] | No | Web device make names. Fetch valid values from GET .../reference/web/device-makes. |
device_oses | string[] | No | Web device OS names. Fetch valid values from GET .../reference/web/device-oses. |
browsers | string[] | No | Web browser names. Fetch valid values from GET .../reference/web/browsers. |
max_devices_per_ip | integer | No | Match strictness: how many devices may share one household/IP. 1 (very strict) to 5 (more reach). Defaults to 3 (recommended). |
CTV fields:
| Field | Type | Required | Description |
|---|---|---|---|
ctv_vendor_names | string[] | No | CTV vendor names. Fetch valid values from GET .../reference/ctv/vendors. |
ctv_content_type_names | string[] | No | CTV content type names. Fetch valid values from GET .../reference/ctv/content-types. |
ctv_content_genre_names | string[] | No | CTV content genre names. Fetch valid values from GET .../reference/ctv/content-genres. |
ctv_channel_names | string[] | No | CTV channel names. Fetch valid values from GET .../reference/ctv/channel-names. |
iab_codes | array | No | IAB codes. Fetch valid values from GET .../reference/web/iab-categories. |
ctv_device_types | string[] | No | CTV device type names. Fetch valid values from GET .../reference/ctv/device-types. |
ctv_device_makes | string[] | No | CTV device make names. Fetch valid values from GET .../reference/ctv/device-makes. |
ctv_device_oses | string[] | No | CTV device OS names. Fetch valid values from GET .../reference/ctv/device-oses. |
ctv_connection_types | string[] | No | CTV connection type names. Fetch valid values from GET .../reference/ctv/connection-types. |
ctv_isps | string[] | No | CTV ISP names. Fetch valid values from GET .../reference/ctv/isps. |
ctv_series | string[] | No | CTV series names. Fetch valid values from GET .../reference/ctv/series. |
Cohorts fields:
| Field | Type | Required | Description |
|---|---|---|---|
cohort_id | integer | Yes | A completed cohort id owned by your company. Fetch valid ids from GET .../reference/cohorts/get-cohorts, or create one from your own cloud file via Create Cohort. (No dates or location.) |
Apps fields:
| Field | Type | Required | Description |
|---|---|---|---|
app_ids | integer[] | At least one of categories / taxonomies / bundle_ids / app_ids | App ids to target. Fetch valid values from GET .../reference/apps/bundle-ids. |
bundle_ids | string[] | At least one of categories / taxonomies / bundle_ids / app_ids | App bundle ids to target. Fetch valid values from GET .../reference/apps/bundle-ids. |
categories | array | At least one of categories / taxonomies / bundle_ids / app_ids | App category ids. Fetch valid values from GET .../reference/apps/categories. |
taxonomies | array | At least one of categories / taxonomies / bundle_ids / app_ids | App taxonomy ids. Fetch valid values from GET .../reference/apps/taxonomies. |
device_os | string[] | No | App device OS values. Fetch valid values from GET .../reference/apps/os. |
refine | object | No | Post-build refinement: keep only the most active devices. Permission-gated, single-dataset audiences only - see Refine. |
Apps location supports countries only: sending location.states,
location.cities, location.dmas, or location.zipcodes on an Apps dataset
is rejected with 422.
POI fields:
Targets people by the points of interest they physically visited. A POI
dataset picks its places in one of two modes, set by poi_selection_mode: by
categories, brands and locations (categorization, the default), or by the
attributes scheduled at locations (attributes, see POI attributes).
The POI catalogs cascade: segments -> categories (?segments[]) -> brands
(?categories[]) -> locations (?brands[]). Unknown POI category, brand and
location ids are rejected with 422, and so is a location whose brand is not
available to your company.
| Field | Type | Required | Description |
|---|---|---|---|
categories | integer[] | At least one of categories / analysisdata / locations in categorization mode, absent or empty in attributes mode | POI category ids, as a FLAT list of integers ([12, 13]). An object here, such as [{"id": 12, "brands": [3]}], is rejected with 422. Fetch valid values from GET .../reference/poi/categories (cascades from .../poi/segments). With no analysisdata (and no locations to take brands from), each category expands to the brands available to your company; a category with none is rejected with 422. |
analysisdata | integer[] | At least one of categories / analysisdata / locations in categorization mode, absent or empty in attributes mode | POI brand ids. This is the brand field: there is no brands key on a POI block, and brands are never nested inside categories. Fetch valid values from GET .../reference/poi/brands. |
locations | array | At least one of categories / analysisdata / locations in categorization mode, absent or empty in attributes mode | POI location identifiers, interpreted per location_identifier_type. Fetch candidates from GET .../reference/poi/locations. |
location_identifier_type | string | No | How locations are interpreted: none, location_id, external_id, store_id, placekey, h3_index, or h3_index_integer. |
poi_selection_mode | string | No | How the dataset picks its places. categorization (the default) reads categories, analysisdata and locations. attributes reads poi_attributes, see POI attributes. |
poi_attributes | array | Yes in attributes mode, absent or empty otherwise | The attribute values to build from, as [{ attribute_id, value_ids[] }] with at least one value id per attribute. Fetch attribute ids from GET .../reference/poi/attributes and value ids from GET .../reference/poi/attribute-values. |
location.countries | string[] | Yes | Country codes (at least one). From GET .../reference/common/countries. |
location.states | string[] | No | State codes. Cascade via GET .../reference/common/states. |
location.cities | string[] | No | City names. Cascade via GET .../reference/common/cities. |
location.dmas | string[] | No | DMA market labels. From GET .../reference/common/dmas. |
location.zipcodes | string[] | No | ZIP codes. Cascade from cities via GET .../reference/common/zipcodes (paginated). |
signal_providers | string[] | Yes | BID values identifying the signal source (at least one). From GET .../reference/common/signal-providers. A provider already used on one of your organization’s audiences of the same dataset type is also accepted, as long as it is still a signal provider. |
time_limits | object | No | { process: boolean, start: 0-23, end: 0-23 }. Hour-of-day window applied only when process is true. |
day_limits | object | No | { process: boolean, days: integer[] }. Day-of-week filter applied only when process is true; days use ISO numbering (1 = Monday .. 7 = Sunday). |
distance_limits | object | No | { process: boolean, distance: number, unit: "miles" | "kilometers" }. Radius applied only when process is true. |
refine | object | No | Post-build refinement: keep only the most active devices. Permission-gated, single-dataset audiences only - see Refine. |
filters | string[] | No | Quality filters, none by default: any of anomalous_devices, anomalous_pois, strict_gps - see POI filters. |
POI attributes
In attributes mode a POI dataset is built from attributes scheduled at
locations instead of from categories, brands and locations. Read the attribute
ids from GET .../reference/poi/attributes,
then the value ids of each attribute from
GET .../reference/poi/attribute-values,
and send them in poi_attributes. The dataset is built from the locations
where any of the selected values is scheduled. Only visits inside each
location’s scheduled event windows count.
Leave categories, analysisdata and locations out in this mode: they come
from the locations the values are scheduled at. The selected values must be
scheduled at one location or more, with at least one scheduled event between
start_date and end_date. The dates can cover at most five years (1,827
days), and poi_attributes takes up to 50 attributes with up to 500 value ids
each.
{
"type": "POI",
"start_date": "2026-09-01",
"end_date": "2026-09-28",
"signal_providers": ["BID001"],
"location": { "countries": ["USA"] },
"poi_selection_mode": "attributes",
"poi_attributes": [
{ "attribute_id": 3, "value_ids": [9] }
]
}POI filters
A POI dataset takes the Audience Manager’s POI quality filters as filters.
None apply unless you send them. The Audience Manager turns on
anomalous_devices and anomalous_pois by default, so send both to match an
audience built there.
| Value | What it removes |
|---|---|
anomalous_devices | Devices on Intuizi’s list of known bad devices. |
anomalous_pois | Every visit to locations flagged as anomalous. |
strict_gps | Every visit at a spot where more than 500 devices from one signal provider were seen at that location in the same month: city-center defaults and rounded coordinates that a provider reports for many devices at once. |
Use strict_gps for address-level lists such as offices and small businesses,
where a single spot shared by hundreds of devices cannot be the location’s own
visitors. Do not use it for stores, malls or venues: real crowds also gather on
one spot there, and it removes them too.
The filters apply to every POI dataset in the audience, not to its other
dataset types. When both datasets are POI, send the same filters on both,
or the request is rejected with 422. Unknown values are rejected with 422,
and so is filters on any other dataset type or next to an
AffinityTransactions dataset, where they would not apply.
{
"type": "POI",
"start_date": "2026-06-01",
"end_date": "2026-08-31",
"signal_providers": ["BID001"],
"categories": [101],
"location": { "countries": ["USA"] },
"filters": ["anomalous_devices", "anomalous_pois", "strict_gps"]
}AffinityTransactions fields:
Targets people by what they actually bought. The purchase catalogs cascade:
categories -> sub-categories (?categories[]) -> brands (?categories[],
?subcategories[]). Unknown category, sub-category, brand and demographic
values are rejected with 422.
Affinity purchase data is United States only. location.countries is optional
here and is always stored as ["USA"]; sending any other country code is
rejected with 422, as is location.dmas (the type reads states, cities and
postal codes only).
Affinity purchase data lands one week at a time, and a completed week is only
fully loaded about eleven days after it ends. end_date therefore cannot run
past the most recent fully-settled week - the latest Sunday that is at least 11
days before today (UTC) - and a later date is rejected with 422. This boundary
advances by one week automatically each Thursday, so the newest selectable end
date moves forward on its own; there is no fixed cut-off to track.
| Field | Type | Required | Description |
|---|---|---|---|
categories | integer[] | At least one of categories / brands | Affinity purchase category ids. Fetch valid values from GET .../reference/affinity-transactions/categories. |
brands | string[] | At least one of categories / brands | Affinity brand id strings. Fetch valid values from GET .../reference/affinity-transactions/brands. |
subcategories | string[] | No | Sub-category strings (merchant category descriptions) cascading from categories. Fetch valid values from GET .../reference/affinity-transactions/subcategories. Picked without any brands, they are resolved to the brands they cover. |
incomes | string[] | No | Income bands. Fetch valid values from GET .../reference/affinity-transactions/incomes. |
ages | string[] | No | Age bands. Fetch valid values from GET .../reference/affinity-transactions/ages. |
genders | string[] | No | Gender values. Fetch valid values from GET .../reference/affinity-transactions/genders. |
ethnicities | string[] | No | Ethnicity values. Fetch valid values from GET .../reference/affinity-transactions/ethnicities. |
channels | string[] | No | Purchase channel: B&M (in-store) and/or ONLINE. |
spend_min | number | No | Minimum total spend (USD) across the date window. |
spend_max | number | No | Maximum total spend (USD) across the date window. |
txn_min | number | No | Minimum number of transactions across the date window. |
txn_max | number | No | Maximum number of transactions across the date window. |
analysis_spend_frequency | boolean | No | Also compute the spend distribution for the built audience. Defaults to false. |
analysis_transactions_frequency | boolean | No | Also compute the transaction-count distribution for the built audience. Defaults to false. |
max_devices_per_ip | integer | No | Match strictness: how many devices each shopper may expand to. 1 (very strict) to 5 (more reach). Defaults to 3 (recommended). |
An AffinityTransactions dataset block:
{
"type": "AffinityTransactions",
"start_date": "2025-01-01",
"end_date": "2025-03-31",
"signal_providers": ["BID001"],
"categories": [1],
"subcategories": ["Eating Places, Restaurants"],
"brands": ["501"],
"incomes": ["100k-150k"],
"channels": ["B&M"],
"spend_min": 50,
"txn_min": 2,
"max_devices_per_ip": 3,
"location": { "states": ["CA"] }
}Demographics fields:
Targets people by their household demographic attributes. The four dictionaries
are flat - no cascade - and at least one of them must carry a selection.
Unknown values are rejected with 422.
A Demographics dataset reads one monthly vintage of the demographics data, the latest
complete one when the audience or estimate runs. The audience’s dataset entry and the
estimate name that month in vintage (YYYY-MM), and its numbers read as “latest monthly
vintage, as of YYYY-MM”. An audience or estimate made before 2026-10-24 carries no
vintage: it read every monthly vintage at once, so its numbers are about 3 to 4 times
larger.
This type carries no date window and no signal providers: sending
start_date, end_date or signal_providers on a Demographics dataset is
rejected with 422. Its location supports countries only, so
location.states, location.cities, location.zipcodes and location.dmas
are rejected too.
| Field | Type | Required | Description |
|---|---|---|---|
genders | string[] | At least one of genders / ages / marital_statuses / incomes | Gender values. Fetch valid values from GET .../reference/demographics/genders. |
ages | string[] | At least one of genders / ages / marital_statuses / incomes | Age ranges. Fetch valid values from GET .../reference/demographics/ages. |
marital_statuses | string[] | At least one of genders / ages / marital_statuses / incomes | Marital status values. Fetch valid values from GET .../reference/demographics/marital-statuses. |
incomes | string[] | At least one of genders / ages / marital_statuses / incomes | Income ranges. Fetch valid values from GET .../reference/demographics/incomes. |
location.countries | string[] | Yes | Country codes (at least one). From GET .../reference/common/countries. |
A Demographics dataset block:
{
"type": "Demographics",
"genders": ["F"],
"ages": ["25-34", "35-44"],
"incomes": ["100k-150k"],
"location": { "countries": ["USA"] }
}Deidentified fields:
Delivers the deidentified signals themselves for a country and date window,
rather than a targeted device list - so instead of filters it takes fields:
which signal fields the delivery carries.
fields selects output columns only. It does not filter rows: there is no
device, OS or platform filter on this type (fields.userDetails: ["deviceos"]
adds the OS column to the delivery, it does not restrict the delivery to an
OS). A filter-shaped key such as deviceos is rejected with 422.
fields is an object keyed by group (geoLocation, advertising,
userDetails, ipDetails, privacy, general). Every group is optional -
send only the groups you want, or omit fields entirely for the default shape.
Fetch the fields and the group each belongs to from
GET .../reference/deidentified/fields;
an unknown field, or a field sent under a group it does not belong to, is
rejected with 422.
This type is delivered on its own: an audience whose dataset is Deidentified
takes exactly one dataset block, so pairing it with a second one (and therefore
an operator) is rejected with 422.
Its location supports countries only, so location.states, location.cities,
location.zipcodes and location.dmas are rejected. When
location.countries includes USA, the date window may cover at most two weeks
per delivery; a longer window is rejected with 422.
The window can always start on the first day of the previous month or later. A
start_date before the earliest day the Audience Manager offers in any time
zone is rejected with 422, and the message names that day. Your Account
Manager can enable a longer look-back for your account.
| Field | Type | Required | Description |
|---|---|---|---|
fields | object | No | Which signal fields the delivery carries, keyed by group. Fetch the fields and their groups from GET .../reference/deidentified/fields. |
signal_providers | string[] | Yes | BID values identifying the signal source (at least one). From GET .../reference/common/signal-providers. A provider already used on one of your organization’s audiences of the same dataset type is also accepted, as long as it is still a signal provider. |
start_date | string (Y-m-d) | Yes | Window start, inside the look-back described above. |
end_date | string (Y-m-d) | Yes | Window end. At most two weeks after start_date when location.countries includes USA. |
location.countries | string[] | Yes | Country codes (at least one). From GET .../reference/common/countries. |
A Deidentified dataset block. Its dates only show the format: send a window
inside the look-back described above.
{
"type": "Deidentified",
"start_date": "2026-10-01",
"end_date": "2026-10-12",
"signal_providers": ["BID001"],
"fields": {
"geoLocation": ["latitude", "longitude"],
"ipDetails": ["ipisp"],
"general": ["d_utc"]
},
"location": { "countries": ["USA"] }
}ProfileAttributes fields:
Targets people by the profile attributes they carry. An attribute is a category, a key under that category, and the values of that key to match; the values of one attribute are alternatives. Send the attributes in one of two shapes:
profile_attributes: a list of attributes combined with AND - a device must match every one.profile_attribute_groups: 1 to 5 groups, combined with AND. A group withmatch: "all"requires every attribute in it, a group withmatch: "any"at least one. Use it for “all of these, and at least one of those”.
A block takes one of the two, never both (422). A single group with
match: "all" builds exactly the same audience as profile_attributes.
The attribute catalogs cascade: categories -> keys (?category_ids[]) ->
values (?category_ids[], ?key). Each attribute is checked at all three
levels: an unknown category, a key that does not belong to the attribute’s
category, or a value that does not belong to its category and key is rejected
with 422 naming the exact field:
datasets.<i>.profile_attributes.<n>.<field> or
datasets.<i>.profile_attribute_groups.<g>.attributes.<n>.<field>. Indexes
count from 0.
A profile_attributes row takes no group or match: sending either is
rejected with 422, so use profile_attribute_groups to group attributes. An
attribute inside a group takes only category_id, key, and value_ids.
location is not applied to this dataset type. Profile attribute data
carries no geography, so the builder discards the whole location block.
location.countries is therefore optional - it is still accepted, for
backward compatibility with integrations written when it was required, but it
never filters anything. location.states, location.cities,
location.zipcodes and location.dmas are rejected outright.
Profile attribute data is delivered quarterly. start_date and end_date must
fall inside the delivered window published by
GET .../reference/profile-attributes/recency-limits;
a date outside it is rejected with 422. The stored window is expanded out to
whole quarters - start_date to the first day of its quarter and end_date to
the last day of its quarter.
| Field | Type | Required | Description |
|---|---|---|---|
profile_attributes | object[] | One of the two | Attributes (at least one), combined with AND. |
profile_attributes[].category_id | integer | Yes | An attribute category id. Fetch valid values from GET .../reference/profile-attributes/categories. |
profile_attributes[].key | string | Yes | An attribute key under that category. Fetch valid values from GET .../reference/profile-attributes/keys with ?category_ids[]. |
profile_attributes[].value_ids | integer[] | Yes | Value ids of that key (at least one). Fetch valid values from GET .../reference/profile-attributes/values with ?category_ids[] and ?key. |
profile_attribute_groups | object[] | One of the two | 1 to 5 attribute groups, combined with AND. |
profile_attribute_groups[].match | string | Yes | all (every attribute in the group must match) or any (at least one must). |
profile_attribute_groups[].attributes | object[] | Yes | The group’s attributes (at least one), each with category_id, key and value_ids exactly like a profile_attributes row. |
signal_providers | string[] | Yes | BID values identifying the signal source (at least one). From GET .../reference/common/signal-providers. A provider already used on one of your organization’s audiences of the same dataset type is also accepted, as long as it is still a signal provider. |
start_date | string (Y-m-d) | Yes | Window start, inside the delivered window. |
end_date | string (Y-m-d) | Yes | Window end, inside the delivered window. |
location.countries | string[] | No | Accepted but never applied - this dataset has no geography. Safe to omit. |
A ProfileAttributes dataset block (no location needed):
{
"type": "ProfileAttributes",
"start_date": "2026-04-01",
"end_date": "2026-06-30",
"signal_providers": ["BID001"],
"profile_attributes": [
{ "category_id": 1, "key": "auto_intent", "value_ids": [10, 12] },
{ "category_id": 2, "key": "credit_band", "value_ids": [30] }
]
}The same dataset with groups - both attributes of the first group required, and at least one of the second group’s:
{
"type": "ProfileAttributes",
"start_date": "2026-04-01",
"end_date": "2026-06-30",
"signal_providers": ["BID001"],
"profile_attribute_groups": [
{ "match": "all", "attributes": [
{ "category_id": 1, "key": "auto_intent", "value_ids": [10, 12] },
{ "category_id": 2, "key": "credit_band", "value_ids": [30] }
] },
{ "match": "any", "attributes": [
{ "category_id": 3, "key": "hunting", "value_ids": [41] },
{ "category_id": 3, "key": "fishing", "value_ids": [42] }
] }
]
}Origin fields:
Targets people by their home location - where a device originates - rather than by the places it visited. Each device’s home is resolved once per week and placed by point-in-polygon against the ZIP code and DMA boundaries, so every filter below narrows that resolved home location.
Its only filters are geographic: beyond the common signal_providers, dates
and location, it defines no key of its own.
Its data is weekly - a device’s home is resolved once per week, and there is no day in the data - so a sub-week window cannot exist. Any dates are accepted, and the window is widened to the whole Monday-to-Sunday weeks it touches: Tuesday to Friday builds, and is stored as, that full week.
Origin data exists for a limited set of countries. Fetch the covered set from
GET .../reference/common/countries?datasetType=Origin;
a country outside it is rejected with 422.
| Field | Type | Required | Description |
|---|---|---|---|
signal_providers | string[] | Yes | BID values identifying the signal source (at least one). From GET .../reference/common/signal-providers?dataType=Origin. |
start_date | string (Y-m-d) | Yes | Window start. Widened back to the Monday of its week. |
end_date | string (Y-m-d) | Yes | Window end, on or after start_date. Widened forward to the Sunday of its week. |
location.countries | string[] | Yes | Country codes (at least one) with Origin coverage. From GET .../reference/common/countries?datasetType=Origin. |
location.states | string[] | No | State codes. From GET .../reference/common/states. |
location.cities | string[] | No | City names. From GET .../reference/common/cities. |
location.dmas | string[] | No | DMA labels exactly as returned by GET .../reference/common/dmas - the label string, never a numeric code. |
location.zipcodes | string[] | No | ZIP codes. From GET .../reference/common/zipcodes. |
An Origin dataset block:
{
"type": "Origin",
"start_date": "2026-08-31",
"end_date": "2026-09-13",
"signal_providers": ["BID001"],
"location": {
"countries": ["USA"],
"states": ["CA"],
"dmas": ["803 - Los Angeles, CA"]
}
}Refine
The optional per-dataset refine object refines the audience after it is
built: devices are ranked by a behavioral metric and only a subset is kept -
the top X% (percentile), everyone at or above a minimum event count
(threshold), or the top N devices (rank). The refined audience keeps its
full data shape; results_count and every downstream activation reflect the
refined device set.
Refine is available on POI and Apps datasets, and only for
single-dataset audiences - a refine block on any other dataset type, or
on either dataset of a two-dataset audience, is rejected with 422.
Refine is a gated feature. It requires additional permissions that your
Account Manager can enable for your account. If a refine block is present and
the feature is not enabled, the request is rejected with 403 (never silently
ignored). Omitting the block entirely requires nothing.
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | How the subset is selected: percentile (keep the top X% by the metric), threshold (keep devices with at least value events), or rank (keep the top value devices). |
metric | string | Yes | The behavioral metric to rank devices by. POI: visit_count or visit_days. Apps: app_usage_count or app_usage_days. |
value | number | Yes | Meaning depends on type: the percentage to keep for percentile (greater than 0 and less than 100), the minimum event count for threshold, or the number of devices for rank (whole number, at least 1). |
fraud_cutoff_percentile | integer | No | Drop outlier devices above this activity percentile (1-100, e.g. 99) before the cut, so bot-like devices do not crowd out the subset you keep. |
max_value | integer | No | Upper bound on the metric, threshold type only - keeps devices between value and max_value events. Rejected with 422 on the other types. |
A refined POI dataset block:
{
"type": "POI",
"start_date": "2025-01-01",
"end_date": "2025-03-31",
"signal_providers": ["BID001"],
"categories": [101],
"location": { "countries": ["USA"] },
"refine": {
"type": "percentile",
"metric": "visit_count",
"value": 10,
"fraud_cutoff_percentile": 99
}
}Crossvisitation
The optional top-level crossvisitation object narrows the audience to people who
physically visited selected points of interest (POIs) within a date window and
optional time-of-day and distance limits. It sits alongside datasets in the
body and is available only when the request contains a POI dataset - a
crossvisitation block on any other dataset type is rejected. When the block is
present it carries the same requirements as a POI dataset: a date window
(start_date / end_date), at least one country, and exactly one category.
Like the frequency analyses, it is not available for some dataset combinations
(see below).
Crossvisitation is a gated feature. It requires additional permissions that
your Account Manager can enable for your account. If a crossvisitation block is
present and the feature is not enabled, the request is rejected with 403 (never
silently ignored). Omitting the block entirely requires nothing.
POI id sources. Pick the POI values from the read-only
Dataset Types - POI catalogs:
categories from GET .../reference/poi/categories,
brands from GET .../reference/poi/brands,
and locations from GET .../reference/poi/locations.
(Creating and managing the underlying POI data itself is the separate
My POI Data surface.)
| Field | Type | Required | Description |
|---|---|---|---|
start_date | string (Y-m-d) | Yes | Start of the visitation window. |
end_date | string (Y-m-d) | Yes | End of the visitation window. |
poi_level | boolean | No | Resolve at the individual-POI level rather than the parent brand/category. |
filters | string[] | No | Quality filters: any of anomalous_devices, gps_only, anomalous_pois. |
categories | integer[] | Yes (exactly one) | A single POI category id (see above). The block requires exactly one category. |
brands | integer[] | No | POI brand ids (see above). With no brands (and no locations to take them from), the category expands to the brands available to your company; a category with none is rejected with 422. |
locations | array | No | POI location identifiers, interpreted per location_identifier_type. |
location_identifier_type | string | No | How locations are interpreted: none, location_id, external_id, store_id, placekey, h3_index, or h3_index_integer. |
location | object | countries required | Geographic scope { countries[], states[], cities[], dmas[], zipcodes[] } - countries needs at least one entry, the rest are optional. countries come from GET .../reference/common/countries. |
time_limits | object | No | { process: boolean, start: 0-23, end: 0-23 }. Hour-of-day window applied only when process is true. |
day_limits | object | No | { process: boolean, days: integer[] }. Day-of-week filter applied only when process is true; days use ISO numbering (1 = Monday .. 7 = Sunday). |
distance_limits | object | No | { process: boolean, distance: number, unit: "miles" | "kilometers" }. Radius applied only when process is true. |
Frequency analyses
The optional top-level analyses object runs the frequency analyses as the
audience builds - the same toggles the Audience Manager offers. Each one
stores, for every device in the audience, the number of distinct days it was
seen on its dataset:
| Field | Type | Required | Description |
|---|---|---|---|
frequency | boolean | No | true runs the Visitation Frequency analysis: distinct days a device was seen at the POI dataset’s points of interest. Requires a POI dataset. |
frequency_day_part | boolean | No | The Visitation Frequency by Day part sub-option: true buckets the Visitation Frequency by hour of the day instead of by visit day. Requires frequency: true, and is not available when the request holds an Apps dataset. A day-part audience cannot be previewed (hours are not visit counts); its activation applies the Day Part filter instead. |
apps_frequency | boolean | No | true runs the Apps Frequency analysis: distinct days a device used the Apps dataset’s apps. Requires an Apps dataset. |
web_frequency | boolean | No | true runs the Web Frequency analysis: distinct days a device visited the WebDomain dataset’s domains. Requires a WebDomain dataset; the activation preview supports it on single-dataset WebDomain audiences. |
Send the flavour that matches your dataset when you may later want to activate
only the devices seen on a minimum number of days (“2+ visits”). The analysis
stores the distinct-day histogram that
Preview Activation
sums and that the freq_min / freq_max filter of
Create Activation
applies. An audience built without a frequency analysis cannot be previewed or
filtered by frequency, and none can be added after the build. A two-dataset
audience may carry more than one flavour, exactly as in the Audience Manager,
but the preview only resolves an audience that carries exactly one.
The frequency analyses are a gated feature. They require additional
permissions that your Account Manager can enable for your account. If any of
them is true and the feature is not enabled, the request is rejected with
403 (never silently ignored). Omitting the object, or sending false,
requires nothing.
Each flavour needs its dataset. frequency needs a POI block,
apps_frequency an Apps block and web_frequency a WebDomain block: a
flavour sent without its dataset is rejected with 422 naming it, and so is
frequency_day_part without frequency or next to an Apps block. Any other
key under analyses is rejected with 422 naming it.
Some datasets turn an analysis off. Cross visitation, Visitation Frequency
and Apps Frequency are not available for some dataset types, or for some
combinations of two datasets and an operator. The Audience Manager disables
them there, and the API rejects them with 422 on crossvisitation,
analyses.frequency or analyses.apps_frequency.
{
"name": "Coffee shop visitors - January",
"datasets": [
{
"type": "POI",
"start_date": "2026-01-01",
"end_date": "2026-01-31",
"signal_providers": ["BID001"],
"categories": [101],
"location": { "countries": ["USA"] }
}
],
"analyses": { "frequency": true }
}Data Stream Visualizations
An audience can generate a set of charts while it builds - visitation patterns, dwell time, demographics, CTV and web breakdowns and so on - which are then viewed in the Intuizi console on the audience’s Data Streams tab.
Opt in with the optional top-level datastreams array. It takes the same shape
as the one on activations, so a client that already
speaks one speaks both:
| Field | Type | Required | Description |
|---|---|---|---|
datastreams[].id | integer | Yes | The data stream id, from the reference catalog below. |
datastreams[].visualizing_status | boolean | No | true to generate this stream’s visualizations. Omitted or false means the stream is listed but not generated. |
{
"name": "Airport visitors - September",
"datasets": [ { "type": "POI", "start_date": "2026-09-01", "end_date": "2026-09-10" } ],
"datastreams": [
{ "id": 21, "visualizing_status": true },
{ "id": 14, "visualizing_status": true }
]
}The rendered charts are not returned by this API. There is no endpoint that
serves a visualization payload and no PDF export. datastreams controls what
gets built, and the results are read in the console. While the charts are drawn,
the audience reads 109 Visualizing data streams, before 105 and 104
Completed, so keep polling through it (see
Status Codes).
Where the ids come from. List what you may use, scoped to the dataset you are about to build:
GET /api/v2/analyses/reference/common/datastream-visualizations?dataset_type=POIThe list is scoped to the caller: every public stream, plus the private ones
assigned to your company. Streams without an active visualization are never
listed. Each item carries id, slug, name, dataset_types and
visual_count.
Mismatches are rejected, not dropped. A stream that does not apply to this
audience’s dataset types is a 422 naming the stream and the types it does
support - it is not silently ignored, because a stream built against the wrong
dataset produces a broken chart rather than an empty one. An id you are not
permitted to use is reported the same way as one that does not exist.
Cross Purchase
The optional top-level crosspurchase object runs the built audience
against the affinity purchase data: the audience’s devices are matched into
purchase transactions within your date window, scoped to the target categories,
sub-categories and brands you select. It sits alongside datasets in the body.
Because it runs on the finished audience, it is available with any dataset
combination - one or two datasets, any type. The analysis results are surfaced
in the Audience Manager alongside the audience.
Cross purchase is a gated feature. It requires additional permissions that
your Account Manager can enable for your account. If a crosspurchase block is
present and the feature is not enabled, the request is rejected with 403
(never silently ignored). Omitting the block entirely requires nothing.
Target value sources. Pick the target values from the read-only
Transactions catalogs:
target_categories from GET .../reference/affinity-transactions/categories,
target_subcategories from GET .../reference/affinity-transactions/subcategories,
and target_brands from GET .../reference/affinity-transactions/brands.
| Field | Type | Required | Description |
|---|---|---|---|
start_date | string (Y-m-d) | Yes | Start of the purchase window. |
end_date | string (Y-m-d) | Yes | End of the purchase window. The range may cover at most 2 calendar months, and cannot run past the most recent fully-settled purchase week - the latest Sunday at least 11 days before today (UTC), which advances each Thursday. A later date is rejected with 422. |
target_categories | integer[] | At least one of target_categories / target_subcategories / target_brands | Affinity category ids to scope the analysis to. |
target_subcategories | string[] | At least one of target_categories / target_subcategories / target_brands | Affinity sub-category strings (verbatim values from the reference read). |
target_brands | string[] | At least one of target_categories / target_subcategories / target_brands | Affinity brand id strings. |
A crosspurchase block on a request body:
{
"name": "Coffee visitors vs dining purchases",
"datasets": [ { "type": "POI", "...": "..." } ],
"crosspurchase": {
"start_date": "2025-01-01",
"end_date": "2025-02-28",
"target_categories": [1],
"target_brands": ["501"]
}
}WebDomain,
CTV, POI and Origin read the full location block (POI and Origin
also take location.dmas); Apps, Demographics,
Deidentified and ProfileAttributes read
location.countries only;
AffinityTransactions is United States only and reads location.states,
location.cities and location.zipcodes; Cohorts takes neither dates nor
location, and Demographics takes no dates.WebDomain windows roll: start_date must fall within the last 45 days of
available data at the moment you send the request - substitute a current
window for the dates below.
curl -X POST "https://console.intuizi.com/api/v2/analyses/audiences/create" \
-H "Authorization: Bearer <YOUR_TOKEN>" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Idempotency-Key: <UNIQUE_KEY>" \
-d '{
"name": "US auto-intenders on tech sites",
"datasets": [
{
"type": "WebDomain",
"start_date": "2026-07-01",
"end_date": "2026-07-28",
"signal_providers": ["BID001"],
"iab_category_codes": [1, 4],
"web_domains": ["example.com"],
"device_types": ["Mobile"],
"browsers": ["Chrome"],
"languages": ["en"],
"location": { "countries": ["USA"] }
}
]
}'Response
The create response is the freshly queued audience (status Initiating,
results_count 0). credits_charged is the credits the build was charged, or
null unless your organization is billed in credits. The
build’s price is known before you create it, from
Quote Audience.
{
"status": "success",
"code": 201,
"message": "Resource created successfully.",
"data": [
{
"id": 88,
"name": "US auto-intenders on tech sites",
"status": { "id": 100, "name": "Initiating" },
"is_cohort": false,
"is_lookalike": false,
"results_count": 0,
"credits_charged": 4,
"created_at": "2025-01-01 12:00:00"
}
]
}Idempotency
The optional Idempotency-Key header makes a create safe to retry. The key is
caller-generated: any unique string up to 255 characters (a UUID v4 is
typical), one per logical create - reuse it only when retrying that same
request. The first
request with a given key runs normally and the successful (2xx) response is cached
per caller. A retry with the same key and the same body replays the stored
response verbatim with an Idempotency-Replayed: true header, so no duplicate
audience is created. A retry with the same key but a different body returns
409 Conflict. See Idempotency for the full replay
semantics and the eight endpoints that honor the header.
List Audiences
GET /api/v2/analyses/audiences/index
Lists the audiences owned by the authenticated user’s company, most recent first, paginated.
Auth: bearer token + Accept: application/json. Rate limit: read bucket (120
requests/min).
Query parameters
| Field | Type | Required | Description |
|---|---|---|---|
per_page | integer | No | Items per page. Defaults to 25, capped at 100. |
page | integer | No | Page number (standard pagination). |
search | string | No | Optional free-text filter on the audience name (case-insensitive contains). |
curl "https://console.intuizi.com/api/v2/analyses/audiences/index?per_page=25" \
-H "Authorization: Bearer <YOUR_TOKEN>" \
-H "Accept: application/json"Response
{
"status": "success",
"code": 200,
"message": "Resources fetched successfully.",
"data": {
"items": [
{
"id": 88,
"name": "Coffee Buyers NYC",
"status": { "id": 104, "name": "Completed" },
"is_cohort": false,
"is_lookalike": false,
"results_count": 482311,
"credits_charged": null,
"is_activation_allowed": true,
"eligibility": {
"allowed": true,
"reasons": [],
"metrics": {"unique_eids": 482311, "unique_scids": null, "eid_scid_ratio": null, "is_affinity": false}
},
"totals": {"uniques": 482311, "visits": 1203440, "avg_uniques": 16077, "avg_visits": 40114},
"source_audience": null,
"created_by": { "name": "Jane Doe", "email": "jane.doe@acme.example" },
"project": { "id": 4, "name": "Retail 2025" },
"operator": "AND",
"dataset": [
{ "analysis_type": "WebDomain", "start_date": "01/01/2025", "end_date": "03/31/2025" },
{ "analysis_type": "Apps", "start_date": "01/01/2025", "end_date": "03/31/2025" }
],
"created_at": "2025-01-01 12:00:00",
"updated_at": "2025-01-10 09:30:00"
}
],
"pagination": {
"current_page": 1,
"per_page": 25,
"total": 1,
"last_page": 1
}
}
}Get Audience
GET /api/v2/analyses/audiences/{id}
Retrieves a single audience by id.
The same audience is also readable at GET /api/v2/my-data/audiences/{id} for
backward compatibility. The two responses differ by two fields: this
/analyses/audiences/{id} read includes the operator field (the
two-dataset combination mode) and totals (the flat metric whitelist), while
the /my-data/audiences/{id} read has neither - it returns eligibility but
not totals. When the build recorded a month of the demographics data, this
read also carries demographics_stream_vintage, and dataset[].vintage on a
Demographics entry. The /my-data/audiences/{id} read carries neither.
Auth: bearer token + Accept: application/json. Rate limit: read bucket (120
requests/min).
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | Yes | The audience id to fetch. |
curl "https://console.intuizi.com/api/v2/analyses/audiences/88" \
-H "Authorization: Bearer <YOUR_TOKEN>" \
-H "Accept: application/json"Response
created_by is always an object (empty {} when no creator resolves), never
null. is_lookalike is true when this audience is the result of a
Lookalike Model run - false
for every ordinarily-built audience. is_activation_allowed is the verdict of the activation eligibility engine - the same
gates the console UI, the activation endpoints and the scheduler apply:
Gate (reasons[].code) | Rule |
|---|---|
minimum_devices | at least 500 unique devices (1,000 for lookalike outputs) |
affinity_device_coverage | audiences containing AffinityTransactions data: unique EIDs must exceed 2x unique SCIDs |
scid_count_unavailable | Affinity audience built before SCID totals were stored - duplicate and rebuild it |
audience_expired | a notice, not a block: standard audiences whose latest dataset end date is older than 90 days cannot be delivered as MAIDs or IPs (mapping keys are retained for 90 days). blocks_identifiers = ["MAID", "IP"]; activation with an EID / SCID / HEM pricing model is allowed, a MAID or IP pricing model is rejected with 422. Affinity, lookalikes and cohorts are exempt |
When blocked, eligibility.reasons[] carries the code, a human message, the
threshold used and the observed aggregate counts (for Affinity: unique_eids,
unique_scids, eid_scid_ratio, required_unique_eids). eligibility.notices[]
has the same shape plus blocks_identifiers: rules that only apply to some
output identifiers. They leave allowed true; the activation create rejects a
pricing model whose identifiers fall in blocks_identifiers. eligibility.metrics
always returns unique_eids, unique_scids (null when the audience has no
Affinity data), eid_scid_ratio and is_affinity. totals is the same metric
whitelist the console shows (keys vary by dataset type, e.g. uniques, scids,
transactions, amount, visits, signals, unique_eips). Only aggregates are
returned - never raw SCIDs or device identifiers.
dataset is a trimmed summary with one entry per dataset block (one or
two entries). A Cohorts entry also carries cohort, the cohort it reads. A
POI entry built in attributes mode also carries poi_selection_mode and
poi_attributes, the attribute pick it was built from, in the shape
POI attributes describes. A POI entry built from
categories, brands and locations carries neither. A Demographics entry also
carries vintage, the monthly vintage it read (YYYY-MM).
operator carries the combination mode for a two-dataset audience
(AND, OR, or NOTIN), and is null for a single-dataset audience.
demographics_stream_vintage names the month an audience’s demographics
data stream reads (YYYY-MM). It is the month the
build recorded, so an audience that is not a Demographics audience carries it
too, and a Demographics audience shows the same month in dataset[].vintage.
The field is read-only. When the build recorded no month, as for an audience made
before 2026-10-24, both are absent.
"dataset": [
{ "analysis_type": "Demographics", "start_date": "", "end_date": "", "vintage": "2026-09" }
],
"demographics_stream_vintage": "2026-09",Two fields say what an audience was built from, so you can follow it by id instead of by name:
source_audience- the seed audience a Lookalike Model was built from (thesource_audience_idit was created with).nullfor every other audience.dataset[].cohort- on aCohortsentry, the cohort it reads. Get Cohort then says how that cohort was created (source) and, for one built from an audience, which audience (source_audience).
Each is { "id", "name" }, or null when the audience or cohort it points to
has been deleted or does not belong to your company - so an id you get here is
always one you can fetch.
"source_audience": null,
"operator": "AND",
"dataset": [
{ "analysis_type": "Cohorts", "start_date": "", "end_date": "", "cohort": { "id": 42, "name": "Loyalty program members" } },
{ "analysis_type": "CTV", "start_date": "01/27/2026", "end_date": "07/27/2026" }
]A POI entry in attributes mode:
"dataset": [
{
"analysis_type": "POI",
"start_date": "09/01/2026",
"end_date": "09/28/2026",
"poi_selection_mode": "attributes",
"poi_attributes": [{ "attribute_id": 3, "value_ids": [9] }]
}
]The read returns the datasets and operator only. A crossvisitation block or an
analyses object sent on create is not echoed back here - they live outside
the public dataset shape and do not round-trip on read.
credits_charged is the credits the audience’s run was charged, after any
refund: a run that fails, is cancelled, or completes with no results reads 0.
It is null unless your organization is billed in credits,
and for a run from before it was.
recipe_hash and normalized_payload are the canonical recipe stamped at
create time: the same hash a prior
Estimate Audience Size call returned
for the identical payload, and the normalized body it was computed from. Both
are null for audiences created in the console UI or before this field
existed.
{
"status": "success",
"code": 200,
"message": "Resource fetched successfully.",
"data": [
{
"id": 88,
"name": "Coffee Buyers NYC",
"status": { "id": 104, "name": "Completed" },
"is_cohort": false,
"is_lookalike": false,
"results_count": 482311,
"credits_charged": null,
"is_activation_allowed": true,
"eligibility": {
"allowed": true,
"reasons": [],
"metrics": {"unique_eids": 482311, "unique_scids": null, "eid_scid_ratio": null, "is_affinity": false}
},
"totals": {"uniques": 482311, "visits": 1203440, "avg_uniques": 16077, "avg_visits": 40114},
"source_audience": null,
"created_by": { "name": "Jane Doe", "email": "jane.doe@acme.example" },
"project": { "id": 4, "name": "Retail 2025" },
"operator": "AND",
"dataset": [
{ "analysis_type": "WebDomain", "start_date": "01/01/2025", "end_date": "03/31/2025" },
{ "analysis_type": "Apps", "start_date": "01/01/2025", "end_date": "03/31/2025" }
],
"recipe_hash": null,
"normalized_payload": null,
"created_at": "2025-01-01 12:00:00",
"updated_at": "2025-01-10 09:30:00"
}
]
}Estimate Audience Size
POST /api/v2/analyses/audiences/estimate
Answers “how many devices would this audience hold?” without creating an
audience. The request body is identical to
Create Audience - same fields, same
validation, same 422 responses - and the estimate runs the same build
pipeline, so the numbers match what a create with this payload would produce.
Nothing appears in the Audience Manager and no export, cohort, schedule, or
activation is triggered.
The estimate is asynchronous: this call answers at once with status: "pending", and how long the answer takes depends on the datasets. Read it
with
GET /api/v2/analyses/audiences/estimate/{id}
until status is completed, blocked, or failed. Add wait to that read
and it waits up to 15 seconds for the answer instead of answering at once.
An estimate is metered on the bytes it scans, which count toward your
company’s monthly data-scan limit, reported under the estimate operation
type on Usage. When the limit has been reached the call is
refused with the same 422 a create receives, before anything is queued.
The response carries recipe_hash: a canonical hash of the datasets +
operator recipe. Creating an audience with the identical payload stamps the
same hash on the audience (returned by
Get Audience), proving the created audience
matches the estimated recipe. The name and project_id are not part of the
hash - renaming does not invalidate an estimate.
Auth: bearer token + Accept: application/json + Content-Type: application/json. Rate limit: write bucket (30 requests/min). Optional:
Idempotency-Key header (see below).
Body
The body of Create Audience, unchanged:
name and datasets required, operator required for two dataset blocks,
optional project_id and analyses (checked exactly as on create; an estimate
never runs an analysis, and the flags change neither the estimate nor its
recipe_hash). Every dataset block rule on this page applies verbatim.
curl -X POST "https://console.intuizi.com/api/v2/analyses/audiences/estimate" \
-H "Authorization: Bearer <YOUR_TOKEN>" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"name": "Coffee Buyers NYC (probe)",
"datasets": [{ "type": "Cohorts", "cohort_id": 7 }]
}'Response
201 with the queued estimate. estimate is null until the run completes.
{
"status": "success",
"code": 201,
"message": "Resource created successfully.",
"data": [
{
"id": 12,
"status": "pending",
"name": "Coffee Buyers NYC (probe)",
"recipe_hash": "sha256:5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8",
"normalized_payload": {
"name": "Coffee Buyers NYC (probe)",
"operator": "Single",
"datasets": [{ "cohort_id": 7, "type": "Cohorts" }]
},
"estimate": null,
"status_message": null,
"info": null,
"project_id": null,
"created_at": "2026-08-24 12:00:00",
"updated_at": "2026-08-24 12:00:00"
}
]
}Get Audience Estimate
GET /api/v2/analyses/audiences/estimate/{id}
Retrieves one size estimate with its status and, once completed, the numbers.
status walks pending -> processing -> one of three terminal states:
completed (the estimate object holds the numbers), blocked (the recipe
cannot be answered - estimate.blocked is { code, reason }, e.g.
OUT_OF_COVERAGE with the available date window), or failed
(status_message explains).
With wait, the read waits for the answer: it answers as soon as status is
completed, blocked, or failed, and at the latest wait seconds after the
request arrived, with the estimate as it is then. A pending or processing
answer means the estimate is still running: read it again. At most 5 calls of
your organization wait at once (reads with wait and the MCP estimate tools
together), and at most 25 across all organizations. Another call answers at
once, as it would without wait. Only one call waits per estimate: a newer
call for the same estimate takes the wait over, and the older one answers at
once. So a read that answers pending well before its wait has passed
could not wait: pause before the next read, as you would between polls (for
example 5 seconds).
On completed, estimate carries: uniques (approximate distinct device
count - the same counting method real audience totals use, with a standard
error of about 2.3%), visits / signals / unique_eips where the dataset
types produce them, the full totals row a create would have shown,
providers (signal providers present in the result), as_of (when the
numbers were computed), query_metrics (runtime and data scanned), method
and exact (how the count was produced), vintage (a Demographics dataset
only: the monthly vintage its numbers come from, YYYY-MM), and the echoed
canonical recipe_hash.
info is null, or a sentence about what the number leaves out. When a
POI dataset of the recipe is in attributes mode, the estimate does not
account for the event time windows of the selected attributes, and the built
audience will be materially smaller. See POI attributes.
Auth: bearer token + Accept: application/json. Rate limit: read bucket
(120 requests/min).
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | Yes | The estimate id from Estimate Audience Size. |
Query parameters
| Field | Type | Required | Description |
|---|---|---|---|
wait | integer | No | Seconds the read may wait for the answer, from 1 to 15, counted from the moment the request arrives. Any other value is refused (422). Without it, the read answers at once. |
curl "https://console.intuizi.com/api/v2/analyses/audiences/estimate/12?wait=15" \
-H "Authorization: Bearer <YOUR_TOKEN>" \
-H "Accept: application/json"Response
{
"status": "success",
"code": 200,
"message": "Resource fetched successfully.",
"data": [
{
"id": 12,
"status": "completed",
"name": "Coffee Buyers NYC (probe)",
"recipe_hash": "sha256:5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8",
"normalized_payload": {
"name": "Coffee Buyers NYC (probe)",
"operator": "Single",
"datasets": [{ "cohort_id": 7, "type": "Cohorts" }]
},
"estimate": {
"uniques": 482311,
"visits": 1203440,
"signals": null,
"unique_eips": null,
"number_of_days": 30,
"method": "approx_distinct",
"exact": false,
"as_of": "2026-08-24T12:34:56Z",
"providers": ["<provider-bid>"],
"query_metrics": { "total_queries": 9, "total_data_scanned_bytes": 1073741824, "total_runtime_ms": 412000 },
"canonical_hash": "sha256:5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8",
"totals": { "uniques": 482311, "visits": 1203440 },
"blocked": null
},
"status_message": null,
"info": null,
"project_id": null,
"created_at": "2026-08-24 12:00:00",
"updated_at": "2026-08-24 12:07:00"
}
]
}A blocked estimate keeps status: "blocked" and explains itself:
{
"estimate": {
"uniques": null,
"blocked": {
"code": "OUT_OF_COVERAGE",
"reason": "No POI signal data is available for 2020-01-01..2020-02-01. POI data is available 2024-01 to 2026-07. Adjust the date range to overlap the available window."
}
}
}Quote Audience
POST /api/v2/analyses/audiences/quote
Returns the price in credits of the audience a payload would
create, without creating it. The body is identical to
Create Audience, with the same fields,
the same validation, and the same 422 responses. Nothing is created, nothing is
queued, and the quote is free: it counts toward neither your
build budget nor your data-scan limit.
credits is what Create Audience charges for the same payload, as long as the
credit rates stay the same. Quote before a create to show the price and the
credits left after the run.
Only for an organization billed in credits. Any other organization receives
403 before the body is validated. See Credits.
Auth: bearer token + Accept: application/json + Content-Type: application/json. Rate limit: write bucket (30 requests/min).
Body
The body of Create Audience, unchanged. Every dataset block rule on this page applies verbatim.
curl -X POST "https://console.intuizi.com/api/v2/analyses/audiences/quote" \
-H "Authorization: Bearer <YOUR_TOKEN>" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"name": "Mobile gamers US",
"datasets": [{
"type": "Apps",
"start_date": "2026-09-01",
"end_date": "2026-09-30",
"signal_providers": ["BID001"],
"categories": [1],
"location": { "countries": ["USA"] }
}]
}'Response
A month of one Apps category comes to less than one credit, so the audience costs the minimum of 1 and its line carries no credits.
{
"status": "success",
"code": 200,
"message": "Resource fetched successfully.",
"data": [
{
"run_type": "audience",
"credits": 1,
"minimum": 1,
"minimum_applies": true,
"lines": [
{ "dataset": "Apps", "days": 30, "credits": null }
],
"not_charged": [],
"available": 4905,
"available_after": 4904
}
]
}Response fields
| Field | Type | Description |
|---|---|---|
run_type | string | audience. |
credits | integer | The price of the run in credits. |
minimum | integer | The least a run of this type costs. |
minimum_applies | boolean | true when the datasets add up to less than minimum, so the run costs minimum. |
lines | array | The price split over the run’s datasets: dataset (the dataset type, or CrossPurchase for a Cross Purchase), days (the length of its date window, null for a dataset without one), and credits. The credits of the lines add up to the price, and they are null when minimum_applies is true. |
not_charged | array | Dataset types of the run that carry no credits. |
available | integer | Your organization’s credits now. |
available_after | integer | Your organization’s credits after this run. |
Delete Audience
POST /api/v2/analyses/audiences/delete-by-id
Soft-deletes an audience and queues the downstream cleanup.
Auth: bearer token + Accept: application/json + Content-Type: application/json. Rate limit: write bucket (30 requests/min).
Body
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | Yes | The audience id to delete. Must be owned by your company. |
curl -X POST "https://console.intuizi.com/api/v2/analyses/audiences/delete-by-id" \
-H "Authorization: Bearer <YOUR_TOKEN>" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{ "id": 88 }'Response
{
"status": "success",
"code": 200,
"message": "Resource deleted successfully.",
"data": []
}Create Lookalike Audience
POST /api/v2/analyses/audiences/create-lookalike
Builds a Lookalike Model from a completed seed audience. The result is a new
audience (is_lookalike: true) that goes through the normal polling
lifecycle - poll
GET /api/v2/analyses/audiences/{id} for its
status. Lookalike Models are a gated feature: accounts without the
capability receive 403 - it requires additional permissions which need to be
approved by your Account Manager. The seed audience must be a Completed
audience owned by your company, must not itself be a lookalike, and must hold
at least 1,000 devices by default.
Auth: bearer token + Accept: application/json + Content-Type: application/json. Rate limit: write bucket (30 requests/min by default).
Optional: Idempotency-Key header (see Idempotency).
Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Name of the Lookalike Model and its result audience. |
source_audience_id | integer | Yes | The seed audience id this model learns from. Must be owned by your company, Completed, not itself a lookalike, and hold at least 1,000 devices by default. |
config | object | Yes | Model configuration - see the fields below. |
config.target_size | integer | Yes | How many devices the output audience should contain. 1 to 4,000,000 by default. |
config.geo.countries | string[] | Yes | Countries the candidate universe is restricted to (at least one). Candidates come from Origin data, which covers only some countries, and the create accepts any country code: a run in which none of the countries has Origin data fails after it is created, and when only some do, the others are left out of the model without an error. Start from the Origin list, GET .../reference/common/countries?datasetType=Origin. |
config.geo.states | string[] | No | States the candidate universe is restricted to. |
config.signals | string[] | Yes | Data families the model learns from (at least one): poi, apps, demographics, transactions, profile_attributes. They do not all describe the same thing - see Data families below. (web and ctv are withdrawn; requests naming them are rejected with 422.) |
config.exclude_seed_devices | boolean | Yes | When true, the seed audience’s own devices are removed from the output audience. |
config.contrast_audience_id | integer | No | A second completed audience used as negative examples the model contrasts against. Must belong to your company, be Completed, not be a lookalike, and differ from source_audience_id. Omit for an automatic geo-matched sample. |
config.expand_eids | boolean | Yes | When true, each output device is expanded across its linked identifiers. |
Data families
The families differ in what they observe and over what period. Both matter when you read a model’s results, so they are worth knowing before you choose.
| Family | What it observes | Grain | Window |
|---|---|---|---|
poi | Physical visits to places | Device | Last full calendar month |
apps | Mobile app usage | Device | Last full calendar month |
demographics | Age, income, gender, marital status | Device | Current state, no window |
transactions | Card transaction behaviour | Device | One finalized calendar month |
profile_attributes | Household/person profile of the device’s ONE primary person, read from two catalogs at once: the AA consumer layer (household income and net worth, investments, home ownership, dwelling, education, generation, marital status, household composition, occupation) and the Vision Marketing layer (age band, household size, income band, home ownership and value, education, children, lifestyle flags) - one person per device | Device | Current state, latest quarterly profile refresh |
web | Web browsing (withdrawn; models built before 2026-08-29 still report it) | Household | Trailing 7 complete days |
web is household-derived, which is the one asymmetry to keep in mind.
Browsing is observed against a household’s IP address and attributed to the
devices in that household, capped at the few devices most associated with the
address. A device’s web activity therefore means “browsing seen at this
device’s household”, not “browsing done by this device”. Every other family is
observed on the device itself.
web also reads a trailing 7 days rather than a calendar month, because
browsing is a recency signal. Two families in one model can describe different
periods.
Selecting more families is not automatically better. A family only helps when the seed audience’s own devices are covered by it, and coverage varies a great deal between audiences. The completed model reports how much of each cohort each family actually reached, so you can see what a given run drew on.
Reading a completed model back returns a feature_families object recording
exactly that:
"feature_families": {
"registry_version": "2026-08-27.1",
"included": [
{
"name": "web",
"period": "2026-08-21..2026-08-27",
"coverage": {
"seed": 28.85,
"candidates": 60.2,
"devices_with_signal": 978047
}
}
]
}period is the exact date range the family read, which is what makes a run
reproducible for families whose window moves. Families measured over a fixed
calendar month omit it - the table above already says what window each family
uses.
coverage reports the share of your seed devices and of the
candidates (the pool the model scored) that carried the family’s signal.
The percentages are the useful part when a model underperforms: a family that
reached only a small share of your seed had little to learn from, whatever its
coverage of the wider candidate pool. feature_families is
null for audiences that are not lookalike models, and for models created
before this was recorded.
The read also names the seed the model was built from, so you can read the seed back with Get Audience - for example to see which datasets and date windows it holds:
"is_lookalike": true,
"source_audience": { "id": 14988, "name": "Coffee Buyers NYC" }| notification | boolean | No | Whether to send an email on completion. Defaults to true. |
curl -X POST "https://console.intuizi.com/api/v2/analyses/audiences/create-lookalike" \
-H "Authorization: Bearer <YOUR_TOKEN>" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Idempotency-Key: <UNIQUE_KEY>" \
-d '{
"name": "API Lookalike",
"source_audience_id": 14988,
"config": {
"target_size": 100000,
"geo": { "countries": ["USA"], "states": [] },
"signals": ["poi", "apps"],
"exclude_seed_devices": true,
"contrast_audience_id": null,
"expand_eids": false
}
}'Response
The create response is the freshly queued result audience (status
Initiating). credits_charged is the credits the run was charged, or null
unless your organization is billed in credits. The price is
known before you create it, from
Quote Lookalike.
{
"status": "success",
"code": 201,
"message": "Resource created successfully.",
"data": [
{
"id": 15021,
"name": "API Lookalike",
"status": { "id": 100, "name": "Initiating" },
"is_lookalike": true,
"source_audience_id": 14988,
"credits_charged": 120
}
]
}Cancel Lookalike
POST /api/v2/analyses/audiences/cancel-lookalike
Requests cooperative cancellation of an in-flight Lookalike Model run: the
job checks the cancellation flag at its next checkpoint and stops there
rather than being killed immediately. A finished run cannot be cancelled.
Until the run reaches that checkpoint the audience keeps reading 108
Modeling. When it stops, the audience ends at 400 Error, which is final and
never reaches 104, and an
audience.failed webhook is sent.
Audience Manager shows the status as Cancelled on request. A cancel that
arrives once the result is already being published is ignored, and the run
completes at 104.
Auth: bearer token + Accept: application/json + Content-Type: application/json. Rate limit: write bucket (30 requests/min by default).
Body
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | Yes | The lookalike audience id to cancel - the id returned by Create Lookalike Audience. |
curl -X POST "https://console.intuizi.com/api/v2/analyses/audiences/cancel-lookalike" \
-H "Authorization: Bearer <YOUR_TOKEN>" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{ "id": 15021 }'Response
{
"status": "success",
"code": 200,
"message": "Cancellation requested.",
"data": []
}Quote Lookalike
POST /api/v2/analyses/audiences/quote-lookalike
Returns the price in credits of the lookalike a payload would
build, without building it. The body is identical to
Create Lookalike Audience,
with the same validation and the same 403 and 422 responses. A lookalike
costs the same for any size. Nothing is created, and the quote is free.
Only for an organization billed in credits. Any other organization receives
403 before the body is validated. See Credits.
Auth: bearer token + Accept: application/json + Content-Type: application/json. Rate limit: write bucket (30 requests/min).
Body
The body of Create Lookalike Audience, unchanged.
curl -X POST "https://console.intuizi.com/api/v2/analyses/audiences/quote-lookalike" \
-H "Authorization: Bearer <YOUR_TOKEN>" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"name": "API Lookalike",
"source_audience_id": 14988,
"config": {
"target_size": 100000,
"geo": { "countries": ["USA"] },
"signals": ["poi", "apps"],
"exclude_seed_devices": true,
"expand_eids": false
}
}'Response
The fields of Quote Audience, with
run_type lookalike and no lines.
{
"status": "success",
"code": 200,
"message": "Resource fetched successfully.",
"data": [
{
"run_type": "lookalike",
"credits": 120,
"minimum": 25,
"minimum_applies": false,
"lines": [],
"not_charged": [],
"available": 1000,
"available_after": 880
}
]
}