# Create an Audience


An audience is a definition of *who* you want to reach. You build it against one
or two [datasets](/concepts/datasets) by selecting filter values that you read
from [reference data](/guides/working-with-reference-data) first. This guide
covers the single create endpoint, the create-then-poll flow, the fields each
dataset type accepts, combining two datasets with an operator, and the optional
crossvisitation and cross purchase analyses.

There is **one** create endpoint:

`POST /api/v2/analyses/audiences/create`

An audience holds **one or two datasets**. Each dataset travels in the body as an
entry in a `datasets` array with a `type` - fetch the accepted values from
[Get Dataset Types](/api/v2/common/#get-apiv2analysesreferencecommondataset-types).
When you supply two datasets you combine them with an `operator`
(`AND`, `OR`, or `NOTIN`); a single dataset takes no operator. The exact,
field-level contract is in the
[API Reference - Audiences]({{< relref "/api/v2/audiences" >}}); this guide is the
narrative.

## Prerequisites

- A bearer token ([Authentication](/getting-started/authentication)).
- The reference values you want to target. Read them first with
  [Working with Reference Data](/guides/working-with-reference-data) - every
  filter below is populated from a reference read, so you send the exact values
  the builder expects. The per-type sections link each field to its read.

## The create flow

1. **Authenticate** and get a bearer token.
2. **Build the dataset body** - pick each dataset's `type`, the window
   (`start_date` / `end_date` for all types except Cohorts and Demographics), and the filters you
   want from reference data. With two datasets, also pick an `operator`.
3. **POST** the body to `/api/v2/analyses/audiences/create`. The response returns
   the new audience id immediately, in status `100` Initiating.
4. **Poll** `GET /api/v2/analyses/audiences/{id}` until the lifecycle status
   reaches `104` Completed.

## The request 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 id owned by your company to file the audience under. |
| `datasets` | object[] | Yes | One or two dataset blocks (array of size 1 or 2). |
| `analyses` | object | No | The frequency analyses: `frequency` (POI, with `frequency_day_part`), `apps_frequency` (Apps) and `web_frequency` (WebDomain) run the analysis the activation preview needs; gated and tied to the dataset type - see [Frequency analyses](/api/v2/audiences#frequency-analysis). |

Each `datasets[i]` block always carries a `type`. Except for `Cohorts` and
`Demographics`, it also carries a `start_date` and `end_date`. The remaining
fields depend on the `type`.
The accepted `type` values are 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).

Send only the filters you want - on each type they are optional on their own; you
just need enough to define a meaningful population.

### Combining two datasets

To target the intersection, union, or exclusion of two populations, supply two
dataset blocks and an `operator`:

- `AND` - people who match **both** datasets.
- `OR` - people who match **either** dataset.
- `NOTIN` - people who match the first dataset but **not** the second.

The `operator` is required whenever you send two datasets, and is rejected if you
send only one. Both datasets are validated exactly the same way (each dataset's ids must exist). A second dataset that is
present but missing its `type` is rejected with `422`.

*`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",
          "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",
              "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",
          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',
              '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 >}}

### A two-dataset request

This example combines a `WebDomain` dataset with an `Apps` dataset using `AND`, so
the audience is the people who match **both**:

*`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": "Auto-intenders who also run finance apps",
      "operator": "AND",
      "datasets": [
        {
          "type": "WebDomain",
          "start_date": "2026-07-01",
          "end_date": "2026-07-28",
          "iab_category_codes": [1, 4],
          "location": { "countries": ["USA"] }
        },
        {
          "type": "Apps",
          "start_date": "2026-07-01",
          "end_date": "2026-07-28",
          "categories": [12],
          "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": "Auto-intenders who also run finance apps",
          "operator": "AND",
          "datasets": [
              {
                  "type": "WebDomain",
                  "start_date": "2026-07-01",
                  "end_date": "2026-07-28",
                  "iab_category_codes": [1, 4],
                  "location": {"countries": ["USA"]},
              },
              {
                  "type": "Apps",
                  "start_date": "2026-07-01",
                  "end_date": "2026-07-28",
                  "categories": [12],
                  "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: "Auto-intenders who also run finance apps",
        operator: "AND",
        datasets: [
          {
            type: "WebDomain",
            start_date: "2026-07-01",
            end_date: "2026-07-28",
            iab_category_codes: [1, 4],
            location: { countries: ["USA"] },
          },
          {
            type: "Apps",
            start_date: "2026-07-01",
            end_date: "2026-07-28",
            categories: [12],
            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' => 'Auto-intenders who also run finance apps',
          'operator' => 'AND',
          'datasets' => [
              [
                  'type' => 'WebDomain',
                  'start_date' => '2026-07-01',
                  'end_date' => '2026-07-28',
                  'iab_category_codes' => [1, 4],
                  'location' => ['countries' => ['USA']],
              ],
              [
                  'type' => 'Apps',
                  'start_date' => '2026-07-01',
                  'end_date' => '2026-07-28',
                  'categories' => [12],
                  'location' => ['countries' => ['USA']],
              ],
          ],
      ]);
  $audienceId = $res->json('data.0.id');
  ```
  {{< /tab >}}

{{< /tabs >}}

{{< callout type="info" >}}
The `Idempotency-Key` header is optional and **you generate it yourself** - any
unique string (a UUID v4 is typical), one per logical create. Reuse the same key
only when retrying the exact same request: the retry replays the original
response (`Idempotency-Replayed: true`) instead of double-creating the audience;
the same key with a different body returns `409`.

{{< /callout >}}

## The response

The create response is the freshly queued audience, in status `100` Initiating
with `results_count` 0. The standard `{ status, code, message, data }`
[envelope](/concepts/envelope) wraps it.

```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,
      "results_count": 0,
      "created_at": "2025-01-01 12:00:00"
    }
  ]
}
```

## Dataset fields by type

Every field below is optional unless marked otherwise. The fields backed by a
reference catalog link to the read that returns their valid values - fetch those
first, then pass the values you collected.

### Fields common to all types

These travel directly on each `datasets[i]` block (except where a type opts out,
noted per section).

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `type` | string | Yes | The dataset type. Fetch the types your company is entitled to from [Get Dataset Types](/api/v2/common/#get-apiv2analysesreferencecommondataset-types) - a type not enabled for your account is rejected with `422`. |
| `start_date` | string (`Y-m-d`) | Yes (except Cohorts and Demographics) | Window start date. |
| `end_date` | string (`Y-m-d`) | Yes (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](/api/v2/common/#get-apiv2analysesreferencecommonsignal-providers). |
| `languages` | string[] | No | Language codes. Fetch valid values from [GET /api/v2/analyses/reference/common/languages](/api/v2/common/#get-apiv2analysesreferencecommonlanguages). |
| `location` | object | `countries` required (all types except Cohorts and Transactions) | A `{ countries[], states[], cities[], dmas[], zipcodes[] }` block. `countries` come from [GET /api/v2/analyses/reference/common/countries](/api/v2/common/#get-apiv2analysesreferencecommoncountries); `states` cascade from countries via [GET /api/v2/analyses/reference/common/states](/api/v2/common/#get-apiv2analysesreferencecommonstates); `cities` cascade from states via [GET /api/v2/analyses/reference/common/cities](/api/v2/common/#get-apiv2analysesreferencecommoncities); `dmas` (POI and Origin only) come from [GET /api/v2/analyses/reference/common/dmas](/api/v2/common/#get-apiv2analysesreferencecommondmas). |

`Cohorts` takes neither dates nor location. `Apps`, `Demographics`,
`Deidentified` and `ProfileAttributes` read
`location.countries` only; `WebDomain`
and `CTV` read the full `location` block. `POI` **requires**
`location.countries` and also reads `states`, `cities`, `dmas` and `zipcodes`.
`Origin` **requires** `location.countries` and reads the same `states`,
`cities`, `dmas` and `zipcodes`; its window is widened to whole Monday-to-Sunday
weeks.
`AffinityTransactions` is United States only: `location.countries` is optional
there and it reads `states`, `cities` and `zipcodes`; its recency `end_date`
cannot run past the most recent fully-settled purchase week (the latest Sunday
at least 11 days before today, UTC, which advances each Thursday). The
`crosspurchase` analysis reads the same purchase data and shares that same
end-date cap. `WebDomain` reads archive-limited visitation data: its
`start_date` must fall within the last 45 days of available data, and an
earlier date is rejected with `422`.
`Demographics` takes no dates and no signal providers.
`ProfileAttributes` covers whole quarters inside a
delivered window.

### Web Domain

Targets people by the domains they visit, plus their device and location. Set
`type` to `WebDomain`.

`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 /api/v2/analyses/reference/web/iab-categories](/api/v2/reference/web/#get-apiv2analysesreferencewebiab-categories) (and subcategories from [GET /api/v2/analyses/reference/web/iab-subcategories](/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 /api/v2/analyses/reference/web/domains](/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 /api/v2/analyses/reference/web/ref-domains](/api/v2/reference/web/#get-apiv2analysesreferencewebref-domains). |
| `device_types` | string[] | No | Web device type names. Fetch valid values from [GET /api/v2/analyses/reference/web/device-types](/api/v2/reference/web/#get-apiv2analysesreferencewebdevice-types). |
| `device_makes` | string[] | No | Web device make names. Fetch valid values from [GET /api/v2/analyses/reference/web/device-makes](/api/v2/reference/web/#get-apiv2analysesreferencewebdevice-makes). |
| `device_oses` | string[] | No | Web device OS names. Fetch valid values from [GET /api/v2/analyses/reference/web/device-oses](/api/v2/reference/web/#get-apiv2analysesreferencewebdevice-oses). |
| `browsers` | string[] | No | Web browser names. Fetch valid values from [GET /api/v2/analyses/reference/web/browsers](/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). |

### Connected TV

Targets people by the streaming content and devices they use. Set `type` to
`CTV`. CTV filters are matched by **name**, not numeric id.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `ctv_vendor_names` | string[] | No | CTV vendor names. Fetch valid values from [GET /api/v2/analyses/reference/ctv/vendors](/api/v2/reference/ctv/#get-apiv2analysesreferencectvvendors). |
| `ctv_content_type_names` | string[] | No | CTV content type names. Fetch valid values from [GET /api/v2/analyses/reference/ctv/content-types](/api/v2/reference/ctv/#get-apiv2analysesreferencectvcontent-types). |
| `ctv_content_genre_names` | string[] | No | CTV content genre names. Fetch valid values from [GET /api/v2/analyses/reference/ctv/content-genres](/api/v2/reference/ctv/#get-apiv2analysesreferencectvcontent-genres). |
| `ctv_channel_names` | string[] | No | CTV channel names. Fetch valid values from [GET /api/v2/analyses/reference/ctv/channel-names](/api/v2/reference/ctv/#get-apiv2analysesreferencectvchannel-names). |
| `iab_codes` | array | No | IAB codes. Fetch valid values from [GET /api/v2/analyses/reference/web/iab-categories](/api/v2/reference/web/#get-apiv2analysesreferencewebiab-categories). |
| `ctv_device_types` | string[] | No | CTV device type names. Fetch valid values from [GET /api/v2/analyses/reference/ctv/device-types](/api/v2/reference/ctv/#get-apiv2analysesreferencectvdevice-types). |
| `ctv_device_makes` | string[] | No | CTV device make names. Fetch valid values from [GET /api/v2/analyses/reference/ctv/device-makes](/api/v2/reference/ctv/#get-apiv2analysesreferencectvdevice-makes). |
| `ctv_device_oses` | string[] | No | CTV device OS names. Fetch valid values from [GET /api/v2/analyses/reference/ctv/device-oses](/api/v2/reference/ctv/#get-apiv2analysesreferencectvdevice-oses). |
| `ctv_connection_types` | string[] | No | CTV connection type names. Fetch valid values from [GET /api/v2/analyses/reference/ctv/connection-types](/api/v2/reference/ctv/#get-apiv2analysesreferencectvconnection-types). |
| `ctv_isps` | string[] | No | CTV ISP names. Fetch valid values from [GET /api/v2/analyses/reference/ctv/isps](/api/v2/reference/ctv/#get-apiv2analysesreferencectvisps). |
| `ctv_series` | string[] | No | CTV series names. Fetch valid values from [GET /api/v2/analyses/reference/ctv/series](/api/v2/reference/ctv/#get-apiv2analysesreferencectvseries). |

### Cohorts

Reuses one of your own completed cohorts as an audience. Set `type` to `Cohorts`.
Cohorts are **company-scoped**: you can only target a completed cohort that
belongs to your company. This type takes no dates and no location.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `cohort_id` | integer | Yes | A completed cohort id owned by your company. Fetch valid ids from [GET /api/v2/analyses/reference/cohorts/get-cohorts](/api/v2/reference/cohorts/#get-apiv2analysesreferencecohortsget-cohorts). |

### Apps

Targets people by the mobile apps they have and the categories those apps belong
to. Set `type` to `Apps`. `Apps` reads `location.countries` only.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `app_ids` | integer[] | At least one of `categories` / `taxonomies` / `bundle_ids` / `app_ids` | App ids to target. Fetch valid ids from [GET /api/v2/analyses/reference/apps/bundle-ids](/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 /api/v2/analyses/reference/apps/bundle-ids](/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 /api/v2/analyses/reference/apps/categories](/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 /api/v2/analyses/reference/apps/taxonomies](/api/v2/reference/apps/#get-apiv2analysesreferenceappstaxonomies). |
| `device_os` | string[] | No | App device OS values. Fetch valid values from [GET /api/v2/analyses/reference/apps/os](/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-the-audience). |

App tags narrow the targeted set; fetch valid values from
[GET /api/v2/analyses/reference/apps/tags](/api/v2/reference/apps/#get-apiv2analysesreferenceappstags).

Apps location supports **countries only**: sending `location.states`,
`location.cities`, `location.dmas`, or `location.zipcodes` on an `Apps` dataset
is rejected with `422`.

### POI

Targets people by the points of interest (POIs) they physically visited. Set
`type` to `POI`. The POI catalogs form a **cascade** - pick segments, use them to
read categories, categories to read brands, and brands to read individual
locations:

```
segments -> categories (?segments[]) -> brands (?categories[]) -> locations (?brands[])
```

Unlike the other dataset types, `POI` **requires** `location.countries` (at least
one country). The dataset also accepts an optional time-of-day window
(`time_limits`) and a distance radius (`distance_limits`).

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `categories` | integer[] | At least one of `categories` / `analysisdata` / `locations` | POI category ids. Fetch valid values from [GET /api/v2/analyses/reference/poi/categories](/api/v2/reference/poi/#get-apiv2analysesreferencepoicategories) (cascades from segments read via [GET /api/v2/analyses/reference/poi/segments](/api/v2/reference/poi/#get-apiv2analysesreferencepoisegments)). |
| `analysisdata` | integer[] | At least one of `categories` / `analysisdata` / `locations` | POI **brand** ids. Fetch valid values from [GET /api/v2/analyses/reference/poi/brands](/api/v2/reference/poi/#get-apiv2analysesreferencepoibrands) (cascades from categories). |
| `locations` | array | At least one of `categories` / `analysisdata` / `locations` | POI location identifiers. Fetch candidates from [GET /api/v2/analyses/reference/poi/locations](/api/v2/reference/poi/#get-apiv2analysesreferencepoilocations) (cascades from brands). How the values are interpreted is set by `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`. |
| `signal_providers` | string[] | **Yes** | BID values identifying the signal source. Fetch valid values from [GET /api/v2/analyses/reference/common/signal-providers](/api/v2/common/#get-apiv2analysesreferencecommonsignal-providers). |
| `location.countries` | string[] | **Yes** | Country codes (at least one). Fetch from [GET /api/v2/analyses/reference/common/countries](/api/v2/common/#get-apiv2analysesreferencecommoncountries). |
| `location.states` | string[] | No | State codes. Cascade from countries via [GET /api/v2/analyses/reference/common/states](/api/v2/common/#get-apiv2analysesreferencecommonstates). |
| `location.cities` | string[] | No | City names. Cascade from states via [GET /api/v2/analyses/reference/common/cities](/api/v2/common/#get-apiv2analysesreferencecommoncities). |
| `location.dmas` | string[] | No | DMA market labels. Fetch from [GET /api/v2/analyses/reference/common/dmas](/api/v2/common/#get-apiv2analysesreferencecommondmas). |
| `location.zipcodes` | string[] | No | ZIP codes. Cascade from cities via [GET /api/v2/analyses/reference/common/zipcodes](/api/v2/common/#get-apiv2analysesreferencecommonzipcodes) (paginated). |
| `time_limits` | object | No | `{ process: boolean, start: 0-23, end: 0-23 }` - restrict to an hour-of-day window when `process` is true. |
| `day_limits` | object | No | `{ process: boolean, days: integer[] }` - restrict to days of the week when `process` is true; ISO numbering (1 = Monday .. 7 = Sunday). |
| `distance_limits` | object | No | `{ process: boolean, distance: number, unit: "miles" \| "kilometers" }` - restrict to visits within a radius 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-audience). |

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.

### Transactions {#affinity-transactions}

Targets people by what they actually bought. Set `type` to
`AffinityTransactions`. The purchase catalogs form a **cascade** - pick
categories, use them to read sub-categories, and both to read brands:

```
categories -> subcategories (?categories[]) -> brands (?categories[], ?subcategories[])
```

At least one `categories` or `brands` value is required - without one the
dataset has no purchase filter. Sub-categories are the merchant category
description strings the sub-categories read returns (not ids); picked without
any `brands`, they are resolved to the brands they cover.

Affinity purchase data is United States only, so `location.countries` is
optional here and always stored as `["USA"]`. Sending any other country code is
rejected with `422`, as is `location.dmas` - this type reads `location.states`,
`location.cities` and `location.zipcodes` only.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `categories` | integer[] | At least one of `categories` / `brands` | Affinity purchase category ids. Fetch valid values from [GET /api/v2/analyses/reference/affinity-transactions/categories](/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 /api/v2/analyses/reference/affinity-transactions/brands](/api/v2/reference/affinity-transactions/#get-apiv2analysesreferenceaffinity-transactionsbrands) (cascades from categories and sub-categories). |
| `subcategories` | string[] | No | Sub-category strings cascading from `categories`. Fetch valid values from [GET /api/v2/analyses/reference/affinity-transactions/subcategories](/api/v2/reference/affinity-transactions/#get-apiv2analysesreferenceaffinity-transactionssubcategories). |
| `incomes` | string[] | No | Income bands. Fetch valid values from [GET /api/v2/analyses/reference/affinity-transactions/incomes](/api/v2/reference/affinity-transactions/#get-apiv2analysesreferenceaffinity-transactionsincomes). |
| `ages` | string[] | No | Age bands. Fetch valid values from [GET /api/v2/analyses/reference/affinity-transactions/ages](/api/v2/reference/affinity-transactions/#get-apiv2analysesreferenceaffinity-transactionsages). |
| `genders` | string[] | No | Gender values. Fetch valid values from [GET /api/v2/analyses/reference/affinity-transactions/genders](/api/v2/reference/affinity-transactions/#get-apiv2analysesreferenceaffinity-transactionsgenders). |
| `ethnicities` | string[] | No | Ethnicity values. Fetch valid values from [GET /api/v2/analyses/reference/affinity-transactions/ethnicities](/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). |

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

Targets people by their household demographic attributes. Set `type` to
`Demographics`. The four dictionaries are flat - there is no cascade - and at
least one of them must carry a selection, otherwise the dataset has no filter.

This type carries **no date window and no signal providers**: sending
`start_date`, `end_date` or `signal_providers` is rejected with `422`. Its
location supports countries only (at least one is required), 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 /api/v2/analyses/reference/demographics/genders](/api/v2/reference/demographics/#get-apiv2analysesreferencedemographicsgenders). |
| `ages` | string[] | At least one of `genders` / `ages` / `marital_statuses` / `incomes` | Age ranges. Fetch valid values from [GET /api/v2/analyses/reference/demographics/ages](/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 /api/v2/analyses/reference/demographics/marital-statuses](/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 /api/v2/analyses/reference/demographics/incomes](/api/v2/reference/demographics/#get-apiv2analysesreferencedemographicsincomes). |
| `location.countries` | string[] | **Yes** | Country codes (at least one). Fetch valid values from [GET /api/v2/analyses/reference/common/countries](/api/v2/common/#get-apiv2analysesreferencecommoncountries). |

```json
{
  "type": "Demographics",
  "genders": ["F"],
  "ages": ["25-34", "35-44"],
  "incomes": ["100k-150k"],
  "location": { "countries": ["USA"] }
}
```

### Deidentified

Delivers the deidentified signals themselves for a country and date window,
rather than a targeted device list. Set `type` to `Deidentified`. Instead of
filters it takes `fields`: which signal fields the delivery carries.

`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 /api/v2/analyses/reference/deidentified/fields](/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.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `fields` | object | No | Which signal fields the delivery carries, keyed by group. Fetch the fields and their groups from [GET /api/v2/analyses/reference/deidentified/fields](/api/v2/reference/deidentified/#get-apiv2analysesreferencedeidentifiedfields). |
| `signal_providers` | string[] | **Yes** | BID values identifying the signal source (at least one). Fetch valid values from [GET /api/v2/analyses/reference/common/signal-providers](/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). Fetch valid values from [GET /api/v2/analyses/reference/common/countries](/api/v2/common/#get-apiv2analysesreferencecommoncountries). |

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

### Profile Attributes

Targets people by the profile attributes they carry. Set `type` to
`ProfileAttributes`. `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 -
combined with **AND**, so a device must match every row.

The attribute catalogs form a **three-level cascade** - pick a category, use it
to read the keys under it, and both to read that key's values:

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

Its location supports countries only, so `location.states`, `location.cities`,
`location.zipcodes` and `location.dmas` are rejected.

Profile attribute data is delivered quarterly. `start_date` and `end_date` must
fall inside the delivered window published by
[GET /api/v2/analyses/reference/profile-attributes/recency-limits](/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 /api/v2/analyses/reference/profile-attributes/categories](/api/v2/reference/profile-attributes/#get-apiv2analysesreferenceprofile-attributescategories). |
| `profile_attributes[].key` | string | **Yes** | An attribute key under that category. Fetch valid values from [GET /api/v2/analyses/reference/profile-attributes/keys](/api/v2/reference/profile-attributes/#get-apiv2analysesreferenceprofile-attributeskeys) (cascades from categories). |
| `profile_attributes[].value_ids` | integer[] | **Yes** | Value ids of that key (at least one). Fetch valid values from [GET /api/v2/analyses/reference/profile-attributes/values](/api/v2/reference/profile-attributes/#get-apiv2analysesreferenceprofile-attributesvalues) (cascades from categories and the key). |
| `signal_providers` | string[] | **Yes** | BID values identifying the signal source (at least one). Fetch valid values from [GET /api/v2/analyses/reference/common/signal-providers](/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. |

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

Targets people by their **home location** - where a device originates - rather
than by the places it visited. Set `type` to `Origin`. 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 narrows that resolved home location.

Its only filters are geographic: `location.countries` is required, and
`states`, `cities`, `dmas` and `zipcodes` are optional. `signal_providers` is
required as for the other dated types.

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 /api/v2/analyses/reference/common/countries](/api/v2/common/#get-apiv2analysesreferencecommoncountries)
with `?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). Fetch valid values from [GET /api/v2/analyses/reference/common/signal-providers](/api/v2/common/#get-apiv2analysesreferencecommonsignal-providers) with `?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. Fetch valid values from [GET /api/v2/analyses/reference/common/countries](/api/v2/common/#get-apiv2analysesreferencecommoncountries) with `?datasetType=Origin`. |
| `location.states` | string[] | No | State codes. Fetch valid values from [GET /api/v2/analyses/reference/common/states](/api/v2/common/#get-apiv2analysesreferencecommonstates). |
| `location.cities` | string[] | No | City names. Fetch valid values from [GET /api/v2/analyses/reference/common/cities](/api/v2/common/#get-apiv2analysesreferencecommoncities). |
| `location.dmas` | string[] | No | DMA labels exactly as returned by [GET /api/v2/analyses/reference/common/dmas](/api/v2/common/#get-apiv2analysesreferencecommondmas) - the label string, never a numeric code. |
| `location.zipcodes` | string[] | No | ZIP codes. Fetch valid values from [GET /api/v2/analyses/reference/common/zipcodes](/api/v2/common/#get-apiv2analysesreferencecommonzipcodes). |

```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 the audience {#refine-the-audience}

`POI` and `Apps` datasets accept an optional `refine` object that 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 applies to **single-dataset** audiences only - a `refine` block on
either dataset of a two-dataset audience, or on any other dataset type, is
rejected with `422`.

{{< callout type="warning" >}}
**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` - it is never
silently ignored. Omitting the block entirely requires nothing.
{{< /callout >}}

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

For example, to keep only the devices in the top 10% by visit frequency,
dropping the top 1% as likely bots:

```json
{
  "name": "Frequent coffee shop visitors",
  "datasets": [{
    "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

Crossvisitation narrows the audience to people who physically visited
points of interest (POIs) you select - by category, by brand, or by specific
locations - within a date window and optional time-of-day and distance limits. It
travels in the body as an optional top-level `crossvisitation` object alongside
`datasets`.

{{< callout type="warning" >}}
**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` -
it is never silently ignored. Omitting the block entirely requires nothing.
{{< /callout >}}

### Where the POI ids come from

Pick the POI values from the read-only [Dataset Types - POI](/api/v2/reference/poi/)
catalogs - the same source as every other audience-builder field. (Creating and
managing the underlying POI data itself is the separate
[My POI Data](/api/v2/poi/) surface; see [Submit POI Data](/guides/submit-poi-data).)

- `categories` are POI category ids. Fetch them from
  [`GET /api/v2/analyses/reference/poi/categories`](/api/v2/reference/poi/#get-apiv2analysesreferencepoicategories).
- `brands` are POI brand ids. Fetch them from
  [`GET /api/v2/analyses/reference/poi/brands`](/api/v2/reference/poi/#get-apiv2analysesreferencepoibrands).
- `locations` are identifiers for individual POI locations. Fetch candidates
  from [`GET /api/v2/analyses/reference/poi/locations`](/api/v2/reference/poi/#get-apiv2analysesreferencepoilocations).
  The kind of identifier you send is declared by `location_identifier_type`.

### Crossvisitation fields

| 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. Their kind is set by `location_identifier_type`. |
| `location_identifier_type` | string | No | How the `locations` values 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 /api/v2/analyses/reference/common/countries](/api/v2/common/#get-apiv2analysesreferencecommoncountries). |
| `time_limits` | object | No | `{ process: boolean, start: 0-23, end: 0-23 }` - restrict to an hour-of-day window when `process` is true. |
| `day_limits` | object | No | `{ process: boolean, days: integer[] }` - restrict to days of the week when `process` is true; ISO numbering (1 = Monday .. 7 = Sunday). |
| `distance_limits` | object | No | `{ process: boolean, distance: number, unit: "miles" \| "kilometers" }` - restrict to visits within a radius when `process` is true. |

### A crossvisitation example

Crossvisitation is available **only for the POI dataset**. This combines a `POI`
dataset with a crossvisitation block that narrows it to visitors of specific POI
brands within a date window and a distance limit:

{{< 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": "Coffee-shop cross visitors",
      "datasets": [
        {
          "type": "POI",
          "start_date": "2025-01-01",
          "end_date": "2025-03-31",
          "signal_providers": ["BID001"],
          "categories": [10],
          "location": { "countries": ["USA"] }
        }
      ],
      "crossvisitation": {
        "start_date": "2025-01-01",
        "end_date": "2025-03-31",
        "poi_level": true,
        "filters": ["gps_only"],
        "categories": [10],
        "brands": [55, 56],
        "location": { "countries": ["USA"] },
        "distance_limits": { "process": true, "distance": 5, "unit": "miles" }
      }
    }'
  ```
  {{< /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": "Coffee-shop cross visitors",
          "datasets": [{
              "type": "POI",
              "start_date": "2025-01-01",
              "end_date": "2025-03-31",
              "signal_providers": ["BID001"],
              "categories": [10],
              "location": {"countries": ["USA"]},
          }],
          "crossvisitation": {
              "start_date": "2025-01-01",
              "end_date": "2025-03-31",
              "poi_level": True,
              "filters": ["gps_only"],
              "categories": [10],
              "brands": [55, 56],
              "location": {"countries": ["USA"]},
              "distance_limits": {"process": True, "distance": 5, "unit": "miles"},
          },
      },
  )
  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: "Coffee-shop cross visitors",
        datasets: [{
          type: "POI",
          start_date: "2025-01-01",
          end_date: "2025-03-31",
          signal_providers: ["BID001"],
          categories: [10],
          location: { countries: ["USA"] },
        }],
        crossvisitation: {
          start_date: "2025-01-01",
          end_date: "2025-03-31",
          poi_level: true,
          filters: ["gps_only"],
          categories: [10],
          brands: [55, 56],
          location: { countries: ["USA"] },
          distance_limits: { process: true, distance: 5, unit: "miles" },
        },
      }),
    }
  );
  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' => 'Coffee-shop cross visitors',
          'datasets' => [[
              'type' => 'POI',
              'start_date' => '2025-01-01',
              'end_date' => '2025-03-31',
              'signal_providers' => ['BID001'],
              'categories' => [10],
              'location' => ['countries' => ['USA']],
          ]],
          'crossvisitation' => [
              'start_date' => '2025-01-01',
              'end_date' => '2025-03-31',
              'poi_level' => true,
              'filters' => ['gps_only'],
              'categories' => [10],
              'brands' => [55, 56],
              'location' => ['countries' => ['USA']],
              'distance_limits' => ['process' => true, 'distance' => 5, 'unit' => 'miles'],
          ],
      ]);
  $audienceId = $res->json('data.0.id');
  ```
  {{< /tab >}}

{{< /tabs >}}

{{< callout type="info" >}}
Crossvisitation shapes how the audience is built, but it is **not** echoed back on
the read endpoints. `GET /api/v2/analyses/audiences/{id}` returns the datasets and
operator, not the crossvisitation block. Keep your own copy of what you sent.
{{< /callout >}}

## Cross Purchase

Cross purchase 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 travels in the body as an optional top-level `crosspurchase` object
alongside `datasets`. 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.

{{< callout type="warning" >}}
**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` - it
is never silently ignored. Omitting the block entirely requires nothing.
{{< /callout >}}

Pick the target values from the read-only
[Transactions](/api/v2/reference/affinity-transactions) catalogs - they cascade
(categories -> subcategories -> brands), and any mix works: at least one
target across the three arrays is required.

| 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**. |
| `target_categories` | integer[] | At least one of `target_categories` / `target_subcategories` / `target_brands` | Affinity category ids. From [GET /api/v2/analyses/reference/affinity-transactions/categories](/api/v2/reference/affinity-transactions/#get-apiv2analysesreferenceaffinity-transactionscategories). |
| `target_subcategories` | string[] | At least one of `target_categories` / `target_subcategories` / `target_brands` | Affinity sub-category strings. From [GET /api/v2/analyses/reference/affinity-transactions/subcategories](/api/v2/reference/affinity-transactions/#get-apiv2analysesreferenceaffinity-transactionssubcategories). |
| `target_brands` | string[] | At least one of `target_categories` / `target_subcategories` / `target_brands` | Affinity brand id strings. From [GET /api/v2/analyses/reference/affinity-transactions/brands](/api/v2/reference/affinity-transactions/#get-apiv2analysesreferenceaffinity-transactionsbrands). |

For example, to report how a POI audience purchases across dining brands:

```json
{
  "name": "Coffee visitors vs dining purchases",
  "datasets": [{
    "type": "POI",
    "start_date": "2025-01-01",
    "end_date": "2025-03-31",
    "signal_providers": ["BID001"],
    "categories": [101],
    "location": { "countries": ["USA"] }
  }],
  "crosspurchase": {
    "start_date": "2025-01-01",
    "end_date": "2025-02-28",
    "target_categories": [1],
    "target_brands": ["501"]
  }
}
```

## Poll until Completed

Audience creation is asynchronous (see [The Async Model](/concepts/async-model)).
The create response gives you the new audience id; poll it until its lifecycle
status reaches `104` Completed.

```bash
curl "https://console.intuizi.com/api/v2/analyses/audiences/<AUDIENCE_ID>" \
  -H "Authorization: Bearer <YOUR_TOKEN>" \
  -H "Accept: application/json"
```

- `100`-`103`, `105`, `108`, `109` - still processing. Poll again shortly.
  `109` Visualizing data streams means the audience is drawing the data stream
  visualizations it opted into, and `105` DataStreaming comes before `104`.
- `104` Completed - the audience is ready to activate.
- `107` Additional Info - the build stopped and will not continue. Stop
  polling. Audience Manager shows the reason, most often a date range outside
  the data available for the dataset. Fix the request and create the audience
  again.
- `4xx` - an error state. Stop polling and inspect the response.

See [List, Get & Delete](/guides/list-get-and-delete) for reading and removing
audiences, and [Read an Audience](/guides/read-an-audience) for the full read
response.

## Next steps

- [Activate an Audience](/guides/activate-an-audience) to deliver it once it is
  Completed.
- [Build a Lookalike Model](/guides/build-a-lookalike-model) to grow a completed
  audience into a larger set of similar devices.

## Reference

- Reference data: [Working with Reference Data](/guides/working-with-reference-data)
- Dataset concept: [Datasets](/concepts/datasets)
- Polling and retries: [Polling and Rate Limits](/guides/polling-and-rate-limits)
- Field-level contract: [API Reference - Audiences]({{< relref "/api/v2/audiences" >}})
