# 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](/api/v2/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-apiv2analysesaudiencescreate}

`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}`](#get-apiv2analysesaudiencesid) 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](#idempotency)).

The build counts toward your company's monthly data-scan limit (see
[Usage](/api/v2/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`]({{< relref "/api/v2/common" >}}#get-apiv2analysesreferencecommonoperators). |
| `project_id` | integer | No | A [project](/api/v2/projects) 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](#crossvisitation). |
| `crosspurchase` | object | No | Optional cross purchase analysis run on the built audience. Permission-gated - see [below](#crosspurchase). |
| `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](#frequency-analysis). |
| `datastreams` | object[] | No | Data stream visualizations to generate as the audience builds - see [below](#datastream-visualizations). |

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`]({{< relref "/api/v2/common" >}}#get-apiv2analysesreferencecommondataset-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`]({{< relref "/api/v2/common" >}}#get-apiv2analysesreferencecommondataset-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. Fetch valid values from [`GET /api/v2/analyses/reference/common/signal-providers`]({{< relref "/api/v2/common" >}}#get-apiv2analysesreferencecommonsignal-providers). |
| `languages` | string[] | No | Language codes. Fetch valid values from [`GET /api/v2/analyses/reference/common/languages`]({{< relref "/api/v2/common" >}}#get-apiv2analysesreferencecommonlanguages). |
| `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`]({{< relref "/api/v2/common" >}}#get-apiv2analysesreferencecommoncountries); `states` cascade via [`GET .../reference/common/states`]({{< relref "/api/v2/common" >}}#get-apiv2analysesreferencecommonstates); `cities` cascade via [`GET .../reference/common/cities`]({{< relref "/api/v2/common" >}}#get-apiv2analysesreferencecommoncities); `dmas` (POI and Origin only) come from [`GET .../reference/common/dmas`]({{< relref "/api/v2/common" >}}#get-apiv2analysesreferencecommondmas). |

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`]({{< relref "/api/v2/reference/web" >}}#get-apiv2analysesreferencewebiab-categories) (and [`.../web/iab-subcategories`]({{< relref "/api/v2/reference/web" >}}#get-apiv2analysesreferencewebiab-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`]({{< relref "/api/v2/reference/web" >}}#get-apiv2analysesreferencewebdomains); 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`]({{< relref "/api/v2/reference/web" >}}#get-apiv2analysesreferencewebref-domains). |
| `device_types` | string[] | No | Web device type names. Fetch valid values from [`GET .../reference/web/device-types`]({{< relref "/api/v2/reference/web" >}}#get-apiv2analysesreferencewebdevice-types). |
| `device_makes` | string[] | No | Web device make names. Fetch valid values from [`GET .../reference/web/device-makes`]({{< relref "/api/v2/reference/web" >}}#get-apiv2analysesreferencewebdevice-makes). |
| `device_oses` | string[] | No | Web device OS names. Fetch valid values from [`GET .../reference/web/device-oses`]({{< relref "/api/v2/reference/web" >}}#get-apiv2analysesreferencewebdevice-oses). |
| `browsers` | string[] | No | Web browser names. Fetch valid values from [`GET .../reference/web/browsers`]({{< relref "/api/v2/reference/web" >}}#get-apiv2analysesreferencewebbrowsers). |
| `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`]({{< relref "/api/v2/reference/ctv" >}}#get-apiv2analysesreferencectvvendors). |
| `ctv_content_type_names` | string[] | No | CTV content type names. Fetch valid values from [`GET .../reference/ctv/content-types`]({{< relref "/api/v2/reference/ctv" >}}#get-apiv2analysesreferencectvcontent-types). |
| `ctv_content_genre_names` | string[] | No | CTV content genre names. Fetch valid values from [`GET .../reference/ctv/content-genres`]({{< relref "/api/v2/reference/ctv" >}}#get-apiv2analysesreferencectvcontent-genres). |
| `ctv_channel_names` | string[] | No | CTV channel names. Fetch valid values from [`GET .../reference/ctv/channel-names`]({{< relref "/api/v2/reference/ctv" >}}#get-apiv2analysesreferencectvchannel-names). |
| `iab_codes` | array | No | IAB codes. Fetch valid values from [`GET .../reference/web/iab-categories`]({{< relref "/api/v2/reference/web" >}}#get-apiv2analysesreferencewebiab-categories). |
| `ctv_device_types` | string[] | No | CTV device type names. Fetch valid values from [`GET .../reference/ctv/device-types`]({{< relref "/api/v2/reference/ctv" >}}#get-apiv2analysesreferencectvdevice-types). |
| `ctv_device_makes` | string[] | No | CTV device make names. Fetch valid values from [`GET .../reference/ctv/device-makes`]({{< relref "/api/v2/reference/ctv" >}}#get-apiv2analysesreferencectvdevice-makes). |
| `ctv_device_oses` | string[] | No | CTV device OS names. Fetch valid values from [`GET .../reference/ctv/device-oses`]({{< relref "/api/v2/reference/ctv" >}}#get-apiv2analysesreferencectvdevice-oses). |
| `ctv_connection_types` | string[] | No | CTV connection type names. Fetch valid values from [`GET .../reference/ctv/connection-types`]({{< relref "/api/v2/reference/ctv" >}}#get-apiv2analysesreferencectvconnection-types). |
| `ctv_isps` | string[] | No | CTV ISP names. Fetch valid values from [`GET .../reference/ctv/isps`]({{< relref "/api/v2/reference/ctv" >}}#get-apiv2analysesreferencectvisps). |
| `ctv_series` | string[] | No | CTV series names. Fetch valid values from [`GET .../reference/ctv/series`]({{< relref "/api/v2/reference/ctv" >}}#get-apiv2analysesreferencectvseries). |

**`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`]({{< relref "/api/v2/reference/cohorts" >}}#get-apiv2analysesreferencecohortsget-cohorts), or create one from your own cloud file via [Create Cohort]({{< relref "/api/v2/cohorts" >}}#post-apiv2analysescohortscreate). (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`]({{< relref "/api/v2/reference/apps" >}}#get-apiv2analysesreferenceappsbundle-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`]({{< relref "/api/v2/reference/apps" >}}#get-apiv2analysesreferenceappsbundle-ids). |
| `categories` | array | At least one of `categories` / `taxonomies` / `bundle_ids` / `app_ids` | App category ids. Fetch valid values from [`GET .../reference/apps/categories`]({{< relref "/api/v2/reference/apps" >}}#get-apiv2analysesreferenceappscategories). |
| `taxonomies` | array | At least one of `categories` / `taxonomies` / `bundle_ids` / `app_ids` | App taxonomy ids. Fetch valid values from [`GET .../reference/apps/taxonomies`]({{< relref "/api/v2/reference/apps" >}}#get-apiv2analysesreferenceappstaxonomies). |
| `device_os` | string[] | No | App device OS values. Fetch valid values from [`GET .../reference/apps/os`]({{< relref "/api/v2/reference/apps" >}}#get-apiv2analysesreferenceappsos). |
| `refine` | object | No | Post-build refinement: keep only the most active devices. Permission-gated, single-dataset audiences only - see [Refine](#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. The POI catalogs
cascade: segments -> categories (`?segments[]`) -> brands (`?categories[]`) ->
locations (`?brands[]`). Unknown POI category, brand and location ids are rejected
with `422`.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `categories` | integer[] | At least one of `categories` / `analysisdata` / `locations` | 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`]({{< relref "/api/v2/reference/poi" >}}#get-apiv2analysesreferencepoicategories) (cascades from [`.../poi/segments`]({{< relref "/api/v2/reference/poi" >}}#get-apiv2analysesreferencepoisegments)). |
| `analysisdata` | integer[] | At least one of `categories` / `analysisdata` / `locations` | 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`]({{< relref "/api/v2/reference/poi" >}}#get-apiv2analysesreferencepoibrands). |
| `locations` | array | At least one of `categories` / `analysisdata` / `locations` | POI location identifiers, interpreted per `location_identifier_type`. Fetch candidates from [`GET .../reference/poi/locations`]({{< relref "/api/v2/reference/poi" >}}#get-apiv2analysesreferencepoilocations). |
| `location_identifier_type` | string | No | How `locations` are interpreted: `none`, `location_id`, `external_id`, `store_id`, `placekey`, `h3_index`, or `h3_index_integer`. |
| `location.countries` | string[] | **Yes** | Country codes (at least one). From [`GET .../reference/common/countries`]({{< relref "/api/v2/common" >}}#get-apiv2analysesreferencecommoncountries). |
| `location.states` | string[] | No | State codes. Cascade via [`GET .../reference/common/states`]({{< relref "/api/v2/common" >}}#get-apiv2analysesreferencecommonstates). |
| `location.cities` | string[] | No | City names. Cascade via [`GET .../reference/common/cities`]({{< relref "/api/v2/common" >}}#get-apiv2analysesreferencecommoncities). |
| `location.dmas` | string[] | No | DMA market labels. From [`GET .../reference/common/dmas`]({{< relref "/api/v2/common" >}}#get-apiv2analysesreferencecommondmas). |
| `location.zipcodes` | string[] | No | ZIP codes. Cascade from cities via [`GET .../reference/common/zipcodes`]({{< relref "/api/v2/common" >}}#get-apiv2analysesreferencecommonzipcodes) (paginated). |
| `signal_providers` | string[] | **Yes** | BID values identifying the signal source (at least one). From [`GET .../reference/common/signal-providers`]({{< relref "/api/v2/common" >}}#get-apiv2analysesreferencecommonsignal-providers). |
| `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](#refine). |

The console's audience-level quality filters (anomalous devices, GPS-only
signals, anomalous POIs, GPS decimals) are not exposed on this API and default
to off.

**`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`]({{< relref "/api/v2/reference/affinity-transactions" >}}#get-apiv2analysesreferenceaffinity-transactionscategories). |
| `brands` | string[] | At least one of `categories` / `brands` | Affinity brand id strings. Fetch valid values from [`GET .../reference/affinity-transactions/brands`]({{< relref "/api/v2/reference/affinity-transactions" >}}#get-apiv2analysesreferenceaffinity-transactionsbrands). |
| `subcategories` | string[] | No | Sub-category strings (merchant category descriptions) cascading from `categories`. Fetch valid values from [`GET .../reference/affinity-transactions/subcategories`]({{< relref "/api/v2/reference/affinity-transactions" >}}#get-apiv2analysesreferenceaffinity-transactionssubcategories). 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`]({{< relref "/api/v2/reference/affinity-transactions" >}}#get-apiv2analysesreferenceaffinity-transactionsincomes). |
| `ages` | string[] | No | Age bands. Fetch valid values from [`GET .../reference/affinity-transactions/ages`]({{< relref "/api/v2/reference/affinity-transactions" >}}#get-apiv2analysesreferenceaffinity-transactionsages). |
| `genders` | string[] | No | Gender values. Fetch valid values from [`GET .../reference/affinity-transactions/genders`]({{< relref "/api/v2/reference/affinity-transactions" >}}#get-apiv2analysesreferenceaffinity-transactionsgenders). |
| `ethnicities` | string[] | No | Ethnicity values. Fetch valid values from [`GET .../reference/affinity-transactions/ethnicities`]({{< relref "/api/v2/reference/affinity-transactions" >}}#get-apiv2analysesreferenceaffinity-transactionsethnicities). |
| `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:

```json
{
  "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`.

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`]({{< relref "/api/v2/reference/demographics" >}}#get-apiv2analysesreferencedemographicsgenders). |
| `ages` | string[] | At least one of `genders` / `ages` / `marital_statuses` / `incomes` | Age ranges. Fetch valid values from [`GET .../reference/demographics/ages`]({{< relref "/api/v2/reference/demographics" >}}#get-apiv2analysesreferencedemographicsages). |
| `marital_statuses` | string[] | At least one of `genders` / `ages` / `marital_statuses` / `incomes` | Marital status values. Fetch valid values from [`GET .../reference/demographics/marital-statuses`]({{< relref "/api/v2/reference/demographics" >}}#get-apiv2analysesreferencedemographicsmarital-statuses). |
| `incomes` | string[] | At least one of `genders` / `ages` / `marital_statuses` / `incomes` | Income ranges. Fetch valid values from [`GET .../reference/demographics/incomes`]({{< relref "/api/v2/reference/demographics" >}}#get-apiv2analysesreferencedemographicsincomes). |
| `location.countries` | string[] | **Yes** | Country codes (at least one). From [`GET .../reference/common/countries`]({{< relref "/api/v2/common" >}}#get-apiv2analysesreferencecommoncountries). |

A `Demographics` dataset block:

```json
{
  "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`]({{< relref "/api/v2/reference/deidentified" >}}#get-apiv2analysesreferencedeidentifiedfields);
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`.

| 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`]({{< relref "/api/v2/reference/deidentified" >}}#get-apiv2analysesreferencedeidentifiedfields). |
| `signal_providers` | string[] | **Yes** | BID values identifying the signal source (at least one). From [`GET .../reference/common/signal-providers`]({{< relref "/api/v2/common" >}}#get-apiv2analysesreferencecommonsignal-providers). |
| `start_date` | string (`Y-m-d`) | **Yes** | Window start. |
| `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`]({{< relref "/api/v2/common" >}}#get-apiv2analysesreferencecommoncountries). |

A `Deidentified` dataset block:

```json
{
  "type": "Deidentified",
  "start_date": "2026-01-05",
  "end_date": "2026-01-18",
  "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. `profile_attributes` is a
list of attribute rows, each one a category, a key under that category, and the
values of that key to match. Rows are combined with **AND** - a device must
match every row.

The attribute catalogs cascade: categories -> keys (`?category_ids[]`) ->
values (`?category_ids[]`, `?key`). Each row is checked at all three levels: an
unknown category, a key that does not belong to the row's category, or a value
that does not belong to the row's category and key is rejected with `422`
naming the exact row.

`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`]({{< relref "/api/v2/reference/profile-attributes" >}}#get-apiv2analysesreferenceprofile-attributesrecency-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[] | **Yes** | Attribute rows (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`]({{< relref "/api/v2/reference/profile-attributes" >}}#get-apiv2analysesreferenceprofile-attributescategories). |
| `profile_attributes[].key` | string | **Yes** | An attribute key under that category. Fetch valid values from [`GET .../reference/profile-attributes/keys`]({{< relref "/api/v2/reference/profile-attributes" >}}#get-apiv2analysesreferenceprofile-attributeskeys) 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`]({{< relref "/api/v2/reference/profile-attributes" >}}#get-apiv2analysesreferenceprofile-attributesvalues) with `?category_ids[]` and `?key`. |
| `signal_providers` | string[] | **Yes** | BID values identifying the signal source (at least one). From [`GET .../reference/common/signal-providers`]({{< relref "/api/v2/common" >}}#get-apiv2analysesreferencecommonsignal-providers). |
| `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):

```json
{
  "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] }
  ]
}
```

**`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`]({{< relref "/api/v2/common" >}}#get-apiv2analysesreferencecommoncountries);
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`]({{< relref "/api/v2/common" >}}#get-apiv2analysesreferencecommonsignal-providers). |
| `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`]({{< relref "/api/v2/common" >}}#get-apiv2analysesreferencecommoncountries). |
| `location.states` | string[] | No | State codes. From [`GET .../reference/common/states`]({{< relref "/api/v2/common" >}}#get-apiv2analysesreferencecommonstates). |
| `location.cities` | string[] | No | City names. From [`GET .../reference/common/cities`]({{< relref "/api/v2/common" >}}#get-apiv2analysesreferencecommoncities). |
| `location.dmas` | string[] | No | DMA labels exactly as returned by [`GET .../reference/common/dmas`]({{< relref "/api/v2/common" >}}#get-apiv2analysesreferencecommondmas) - the label string, never a numeric code. |
| `location.zipcodes` | string[] | No | ZIP codes. From [`GET .../reference/common/zipcodes`]({{< relref "/api/v2/common" >}}#get-apiv2analysesreferencecommonzipcodes). |

An `Origin` dataset block:

```json
{
  "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 {#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:

```json
{
  "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.

**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]({{< relref "/api/v2/reference/poi" >}}) catalogs:
`categories` from [`GET .../reference/poi/categories`]({{< relref "/api/v2/reference/poi" >}}#get-apiv2analysesreferencepoicategories),
`brands` from [`GET .../reference/poi/brands`]({{< relref "/api/v2/reference/poi" >}}#get-apiv2analysesreferencepoibrands),
and `locations` from [`GET .../reference/poi/locations`]({{< relref "/api/v2/reference/poi" >}}#get-apiv2analysesreferencepoilocations).
(Creating and managing the underlying POI data itself is the separate
[My POI Data]({{< relref "/api/v2/poi" >}}) 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). |
| `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`]({{< relref "/api/v2/common" >}}#get-apiv2analysesreferencecommoncountries). |
| `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 {#frequency-analysis}

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](/api/v2/activations#get-apiv2analysesactivationspreview)
sums and that the `freq_min` / `freq_max` filter of
[Create Activation](/api/v2/activations#post-apiv2analysesactivationscreate)
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.

```json
{
  "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 {#datastream-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](/api/v2/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. |

```json
{
  "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](/concepts/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=POI
```

The 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 {#crosspurchase}

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]({{< relref "/api/v2/reference/affinity-transactions" >}}) catalogs:
`target_categories` from [`GET .../reference/affinity-transactions/categories`]({{< relref "/api/v2/reference/affinity-transactions" >}}#get-apiv2analysesreferenceaffinity-transactionscategories),
`target_subcategories` from [`GET .../reference/affinity-transactions/subcategories`]({{< relref "/api/v2/reference/affinity-transactions" >}}#get-apiv2analysesreferenceaffinity-transactionssubcategories),
and `target_brands` from [`GET .../reference/affinity-transactions/brands`]({{< relref "/api/v2/reference/affinity-transactions" >}}#get-apiv2analysesreferenceaffinity-transactionsbrands).

| 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:

```json
{
  "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"]
  }
}
```

{{< callout type="info" >}}
Collect the ids and values these filters expect from the
[Dataset Types]({{< relref "/api/v2/reference" >}}) endpoints first, then pass them in here. Only `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.
{{< /callout >}}

*`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.*

{{< tabs >}}

  {{< tab name="cURL" >}}
  ```bash
  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"] }
        }
      ]
    }'
  ```
  {{< /tab >}}

  {{< tab name="Python" >}}
  ```python
  import requests

  res = requests.post(
      "https://console.intuizi.com/api/v2/analyses/audiences/create",
      headers={
          "Authorization": "Bearer <YOUR_TOKEN>",
          "Content-Type": "application/json",
          "Accept": "application/json",
          "Idempotency-Key": "<UNIQUE_KEY>",
      },
      json={
          "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"],
              "languages": ["en"],
              "location": {"countries": ["USA"]},
          }],
      },
  )
  audience_id = res.json()["data"][0]["id"]
  ```
  {{< /tab >}}

  {{< tab name="JavaScript" >}}
  ```javascript
  const res = await fetch(
    "https://console.intuizi.com/api/v2/analyses/audiences/create",
    {
      method: "POST",
      headers: {
        Authorization: "Bearer <YOUR_TOKEN>",
        "Content-Type": "application/json",
        Accept: "application/json",
        "Idempotency-Key": "<UNIQUE_KEY>",
      },
      body: JSON.stringify({
        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"],
          languages: ["en"],
          location: { countries: ["USA"] },
        }],
      }),
    }
  );
  const audienceId = (await res.json()).data[0].id;
  ```
  {{< /tab >}}

  {{< tab name="PHP" >}}
  ```php
  $res = Http::withToken('<YOUR_TOKEN>')->acceptJson()
      ->withHeaders(['Idempotency-Key' => '<UNIQUE_KEY>'])
      ->post('https://console.intuizi.com/api/v2/analyses/audiences/create', [
          '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'],
              'languages' => ['en'],
              'location' => ['countries' => ['USA']],
          ]],
      ]);
  $audienceId = $res->json('data.0.id');
  ```
  {{< /tab >}}

{{< /tabs >}}

### Response

The create response is the freshly queued audience (status Initiating,
`results_count` 0).

```json
{
  "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,
      "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](/concepts/idempotency) for the full replay
semantics and the eight endpoints that honor the header.

## List Audiences {#get-apiv2analysesaudiencesindex}

`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). |

{{< tabs >}}

  {{< tab name="cURL" >}}
  ```bash
  curl "https://console.intuizi.com/api/v2/analyses/audiences/index?per_page=25" \
    -H "Authorization: Bearer <YOUR_TOKEN>" \
    -H "Accept: application/json"
  ```
  {{< /tab >}}

  {{< tab name="Python" >}}
  ```python
  import requests

  res = requests.get(
      "https://console.intuizi.com/api/v2/analyses/audiences/index",
      headers={"Authorization": "Bearer <YOUR_TOKEN>", "Accept": "application/json"},
      params={"per_page": 25},
  )
  page = res.json()["data"]
  ```
  {{< /tab >}}

  {{< tab name="JavaScript" >}}
  ```javascript
  const res = await fetch(
    "https://console.intuizi.com/api/v2/analyses/audiences/index?per_page=25",
    { headers: { Authorization: "Bearer <YOUR_TOKEN>", Accept: "application/json" } }
  );
  const page = (await res.json()).data;
  ```
  {{< /tab >}}

  {{< tab name="PHP" >}}
  ```php
  $res = Http::withToken('<YOUR_TOKEN>')->acceptJson()
      ->get('https://console.intuizi.com/api/v2/analyses/audiences/index', ['per_page' => 25]);
  $page = $res->json('data');
  ```
  {{< /tab >}}

{{< /tabs >}}

### Response

```json
{
  "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,
        "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-apiv2analysesaudiencesid}

`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`.

**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. |

{{< tabs >}}

  {{< tab name="cURL" >}}
  ```bash
  curl "https://console.intuizi.com/api/v2/analyses/audiences/88" \
    -H "Authorization: Bearer <YOUR_TOKEN>" \
    -H "Accept: application/json"
  ```
  {{< /tab >}}

  {{< tab name="Python" >}}
  ```python
  import requests

  res = requests.get(
      "https://console.intuizi.com/api/v2/analyses/audiences/88",
      headers={"Authorization": "Bearer <YOUR_TOKEN>", "Accept": "application/json"},
  )
  audience = res.json()["data"][0]
  ```
  {{< /tab >}}

  {{< tab name="JavaScript" >}}
  ```javascript
  const res = await fetch(
    "https://console.intuizi.com/api/v2/analyses/audiences/88",
    { headers: { Authorization: "Bearer <YOUR_TOKEN>", Accept: "application/json" } }
  );
  const audience = (await res.json()).data[0];
  ```
  {{< /tab >}}

  {{< tab name="PHP" >}}
  ```php
  $res = Http::withToken('<YOUR_TOKEN>')->acceptJson()
      ->get('https://console.intuizi.com/api/v2/analyses/audiences/88');
  $audience = $res->json('data.0');
  ```
  {{< /tab >}}

{{< /tabs >}}

### 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](#post-apiv2analysesaudiencescreate-lookalike) 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.
`operator` carries the combination mode for a two-dataset audience
(`AND`, `OR`, or `NOTIN`), and is `null` for a single-dataset audience.

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](#post-apiv2analysesaudiencescreate-lookalike) was built
  from (the `source_audience_id` it was created with). `null` for every other
  audience.
- `dataset[].cohort` - on a `Cohorts` entry, the cohort it reads.
  [Get Cohort](/api/v2/cohorts#get-apiv2analysescohortsid) 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.

```json
"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" }
]
```

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.

`recipe_hash` and `normalized_payload` are the canonical recipe stamped at
create time: the same hash a prior
[Estimate Audience Size](#post-apiv2analysesaudiencesestimate) 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.

```json
{
  "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,
      "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-apiv2analysesaudiencesestimate}

`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](#post-apiv2analysesaudiencescreate)** - 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 and takes roughly as long as a real audience
build. Poll
[`GET /api/v2/analyses/audiences/estimate/{id}`](#get-apiv2analysesaudiencesestimateid)
until `status` is `completed`, `blocked`, or `failed`.

Because it runs the same build, an estimate scans the same data a create
would and counts toward your company's monthly data-scan limit, reported under
the `estimate` operation type on [Usage](/api/v2/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](#get-apiv2analysesaudiencesid)), 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](#idempotency)).

### Body

The body of [Create Audience](#post-apiv2analysesaudiencescreate), 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.

{{< tabs >}}

  {{< tab name="cURL" >}}
  ```bash
  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 }]
    }'
  ```
  {{< /tab >}}

  {{< tab name="Python" >}}
  ```python
  import requests

  res = requests.post(
      "https://console.intuizi.com/api/v2/analyses/audiences/estimate",
      headers={"Authorization": "Bearer <YOUR_TOKEN>", "Accept": "application/json"},
      json={
          "name": "Coffee Buyers NYC (probe)",
          "datasets": [{"type": "Cohorts", "cohort_id": 7}],
      },
  )
  estimate = res.json()["data"][0]
  ```
  {{< /tab >}}

  {{< tab name="JavaScript" >}}
  ```javascript
  const res = await fetch(
    "https://console.intuizi.com/api/v2/analyses/audiences/estimate",
    {
      method: "POST",
      headers: {
        Authorization: "Bearer <YOUR_TOKEN>",
        Accept: "application/json",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        name: "Coffee Buyers NYC (probe)",
        datasets: [{ type: "Cohorts", cohort_id: 7 }],
      }),
    }
  );
  const estimate = (await res.json()).data[0];
  ```
  {{< /tab >}}

  {{< tab name="PHP" >}}
  ```php
  $ch = curl_init("https://console.intuizi.com/api/v2/analyses/audiences/estimate");
  curl_setopt_array($ch, [
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_POST => true,
      CURLOPT_HTTPHEADER => [
          "Authorization: Bearer <YOUR_TOKEN>",
          "Accept: application/json",
          "Content-Type: application/json",
      ],
      CURLOPT_POSTFIELDS => json_encode([
          "name" => "Coffee Buyers NYC (probe)",
          "datasets" => [["type" => "Cohorts", "cohort_id" => 7]],
      ]),
  ]);
  $estimate = json_decode(curl_exec($ch), true)["data"][0];
  ```
  {{< /tab >}}

{{< /tabs >}}

### Response

`201` with the queued estimate. `estimate` is `null` until the run completes.

```json
{
  "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,
      "project_id": null,
      "created_at": "2026-08-24 12:00:00",
      "updated_at": "2026-08-24 12:00:00"
    }
  ]
}
```

## Get Audience Estimate {#get-apiv2analysesaudiencesestimateid}

`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).

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), and the echoed canonical
`recipe_hash`.

**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. |

{{< tabs >}}

  {{< tab name="cURL" >}}
  ```bash
  curl "https://console.intuizi.com/api/v2/analyses/audiences/estimate/12" \
    -H "Authorization: Bearer <YOUR_TOKEN>" \
    -H "Accept: application/json"
  ```
  {{< /tab >}}

  {{< tab name="Python" >}}
  ```python
  import requests

  res = requests.get(
      "https://console.intuizi.com/api/v2/analyses/audiences/estimate/12",
      headers={"Authorization": "Bearer <YOUR_TOKEN>", "Accept": "application/json"},
  )
  estimate = res.json()["data"][0]
  ```
  {{< /tab >}}

  {{< tab name="JavaScript" >}}
  ```javascript
  const res = await fetch(
    "https://console.intuizi.com/api/v2/analyses/audiences/estimate/12",
    { headers: { Authorization: "Bearer <YOUR_TOKEN>", Accept: "application/json" } }
  );
  const estimate = (await res.json()).data[0];
  ```
  {{< /tab >}}

  {{< tab name="PHP" >}}
  ```php
  $ch = curl_init("https://console.intuizi.com/api/v2/analyses/audiences/estimate/12");
  curl_setopt_array($ch, [
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_HTTPHEADER => [
          "Authorization: Bearer <YOUR_TOKEN>",
          "Accept: application/json",
      ],
  ]);
  $estimate = json_decode(curl_exec($ch), true)["data"][0];
  ```
  {{< /tab >}}

{{< /tabs >}}

### Response

```json
{
  "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,
      "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:

```json
{
  "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."
    }
  }
}
```

## Delete Audience {#post-apiv2analysesaudiencesdelete-by-id}

`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. |

{{< tabs >}}

  {{< tab name="cURL" >}}
  ```bash
  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 }'
  ```
  {{< /tab >}}

  {{< tab name="Python" >}}
  ```python
  import requests

  res = requests.post(
      "https://console.intuizi.com/api/v2/analyses/audiences/delete-by-id",
      headers={
          "Authorization": "Bearer <YOUR_TOKEN>",
          "Content-Type": "application/json",
          "Accept": "application/json",
      },
      json={"id": 88},
  )
  ```
  {{< /tab >}}

  {{< tab name="JavaScript" >}}
  ```javascript
  const res = await fetch(
    "https://console.intuizi.com/api/v2/analyses/audiences/delete-by-id",
    {
      method: "POST",
      headers: {
        Authorization: "Bearer <YOUR_TOKEN>",
        "Content-Type": "application/json",
        Accept: "application/json",
      },
      body: JSON.stringify({ id: 88 }),
    }
  );
  ```
  {{< /tab >}}

  {{< tab name="PHP" >}}
  ```php
  $res = Http::withToken('<YOUR_TOKEN>')->acceptJson()
      ->post('https://console.intuizi.com/api/v2/analyses/audiences/delete-by-id', ['id' => 88]);
  ```
  {{< /tab >}}

{{< /tabs >}}

### Response

```json
{
  "status": "success",
  "code": 200,
  "message": "Resource deleted successfully.",
  "data": []
}
```

## Create Lookalike Audience {#post-apiv2analysesaudiencescreate-lookalike}

`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}`](#get-apiv2analysesaudiencesid) 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](#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`]({{< relref "/api/v2/common" >}}#get-apiv2analysesreferencecommoncountries). |
| `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](#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:

```json
"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](#get-apiv2analysesaudiencesid) - for example to
see which datasets and date windows it holds:

```json
"is_lookalike": true,
"source_audience": { "id": 14988, "name": "Coffee Buyers NYC" }
```

| `notification` | boolean | No | Whether to send an email on completion. Defaults to `true`. |

{{< tabs >}}

  {{< tab name="cURL" >}}
  ```bash
  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
      }
    }'
  ```
  {{< /tab >}}

  {{< tab name="Python" >}}
  ```python
  import requests

  res = requests.post(
      "https://console.intuizi.com/api/v2/analyses/audiences/create-lookalike",
      headers={
          "Authorization": "Bearer <YOUR_TOKEN>",
          "Content-Type": "application/json",
          "Accept": "application/json",
          "Idempotency-Key": "<UNIQUE_KEY>",
      },
      json={
          "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": None,
              "expand_eids": False,
          },
      },
  )
  lookalike_id = res.json()["data"][0]["id"]
  ```
  {{< /tab >}}

  {{< tab name="JavaScript" >}}
  ```javascript
  const res = await fetch(
    "https://console.intuizi.com/api/v2/analyses/audiences/create-lookalike",
    {
      method: "POST",
      headers: {
        Authorization: "Bearer <YOUR_TOKEN>",
        "Content-Type": "application/json",
        Accept: "application/json",
        "Idempotency-Key": "<UNIQUE_KEY>",
      },
      body: JSON.stringify({
        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,
        },
      }),
    }
  );
  const lookalikeId = (await res.json()).data[0].id;
  ```
  {{< /tab >}}

  {{< tab name="PHP" >}}
  ```php
  $res = Http::withToken('<YOUR_TOKEN>')->acceptJson()
      ->withHeaders(['Idempotency-Key' => '<UNIQUE_KEY>'])
      ->post('https://console.intuizi.com/api/v2/analyses/audiences/create-lookalike', [
          '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,
          ],
      ]);
  $lookalikeId = $res->json('data.0.id');
  ```
  {{< /tab >}}

{{< /tabs >}}

### Response

The create response is the freshly queued result audience (status
Initiating).

```json
{
  "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
    }
  ]
}
```

## Cancel Lookalike {#post-apiv2analysesaudiencescancel-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`](/api/v2/webhooks#audience-events) 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](#post-apiv2analysesaudiencescreate-lookalike). |

{{< tabs >}}

  {{< tab name="cURL" >}}
  ```bash
  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 }'
  ```
  {{< /tab >}}

  {{< tab name="Python" >}}
  ```python
  import requests

  res = requests.post(
      "https://console.intuizi.com/api/v2/analyses/audiences/cancel-lookalike",
      headers={
          "Authorization": "Bearer <YOUR_TOKEN>",
          "Content-Type": "application/json",
          "Accept": "application/json",
      },
      json={"id": 15021},
  )
  ```
  {{< /tab >}}

  {{< tab name="JavaScript" >}}
  ```javascript
  const res = await fetch(
    "https://console.intuizi.com/api/v2/analyses/audiences/cancel-lookalike",
    {
      method: "POST",
      headers: {
        Authorization: "Bearer <YOUR_TOKEN>",
        "Content-Type": "application/json",
        Accept: "application/json",
      },
      body: JSON.stringify({ id: 15021 }),
    }
  );
  ```
  {{< /tab >}}

  {{< tab name="PHP" >}}
  ```php
  $res = Http::withToken('<YOUR_TOKEN>')->acceptJson()
      ->post('https://console.intuizi.com/api/v2/analyses/audiences/cancel-lookalike', ['id' => 15021]);
  ```
  {{< /tab >}}

{{< /tabs >}}

### Response

```json
{
  "status": "success",
  "code": 200,
  "message": "Cancellation requested.",
  "data": []
}
```

