# Cohorts


Create cohorts from files in your own cloud storage - or from files you
upload to us via the [upload flow](/api/v2/uploads) - list them and poll
their status. A completed cohort is used as a `Cohorts` dataset on the
[Create Audience]({{< relref "/api/v2/audiences" >}}#post-apiv2analysesaudiencescreate)
surface via `cohort_id`. Cohort creation is asynchronous: the create call
returns immediately with a new id, and you poll the get endpoint until the
cohort reaches `4` Completed.

All cohort 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 uses the write bucket (30
requests/min per caller).

## Create Cohort {#post-apiv2analysescohortscreate}

`POST /api/v2/analyses/cohorts/create`

Creates a cohort from a CSV, GZIP or Parquet file - either a file (or folder
of files) in your own **AWS S3** or **Google Cloud Storage** (`file_uri`), or
a file you uploaded via the [upload flow](/api/v2/uploads)
(`upload_reference`, reserved with purpose `cohort`). Send exactly one of the
two. No credentials travel in the request: a cloud location must already be
readable by Intuizi's processing account - your Account Manager can help set
that up. The cohort is queued asynchronously; poll
[`GET /api/v2/analyses/cohorts/{id}`](#get-apiv2analysescohortsid)
for its status.

Not sure which column holds your identifiers? Call
[Preview Cohort File](#post-apiv2analysescohortspreview) first.

**Auth:** bearer token + `Accept: application/json` + `Content-Type:
application/json`. Rate limit: write bucket (30 requests/min). Optional:
`Idempotency-Key` header makes this create safe to retry - see
[Idempotency](/concepts/idempotency).

### Body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | **Yes** | Cohort name (max 255 chars). |
| `project_id` | integer | No | A [project](/api/v2/projects) id owned by your company to file the cohort under. |
| `file_uri` | string | One of | `s3://bucket/path` or `gs://bucket/path` - the file (or folder of files) to import from your own cloud. Must include a bucket and a path (e.g. `s3://my-bucket/exports/devices.csv`). A URI ending in `.csv`, `.gz` or `.parquet` is read as a single file; anything else is read as a folder. The match is case-sensitive, so `Q3.CSV` is read as a folder. Send either this or `upload_reference`, not both. |
| `upload_reference` | string | One of | The `upload_reference` from [Create an Upload](/api/v2/uploads#post-apiv2uploadscreate) (purpose `cohort`), after the file was `PUT` to the presigned URL. One-shot: a reference can create exactly one cohort. The same suffix rule as `file_uri` applies to the `filename` the upload was created with, cut to its first 100 characters: only a name ending in `.csv`, `.gz` or `.parquet` (case-sensitive) is read as a single file, and anything else as a folder. Send either this or `file_uri`, not both. |
| `file_format` | string | **Yes** | `csv` (uncompressed), `gzip` (compressed) or `parquet`. |
| `identifier_type` | string | **Yes** | What the identifier column contains: `eid`, `eid_md5`, `maid`, `ip`, `hem_plaintext`, `scid`, `hem_md5`, `hem_sha1` or `hem_sha256`. Plain-text emails, IPs and MAIDs are encrypted and hashed before analysis begins. |
| `identifier_column` | string | **Yes** | The column in your file holding the identifier. Letters, numbers, hyphens and underscores only. |
| `metadata_columns` | string[] | No | Extra columns from your file to keep alongside the identifiers. |
| `ip_enrichment` | boolean | No | Also add devices seen on the same IP addresses as the cohort's devices (Enrich by Household in the Audience Manager). Defaults to false. |
| `device_limit` | integer | No | Cap the number of devices the cohort keeps. Omit for no cap. |
| `max_devices_per_ip` | integer | No | SCID files only. Match Strictness, 1 (very strict) to 5 (more reach): each person contributes at most `level x 2` of their top-ranked devices. Defaults to 3 (top 6 devices per person). Ignored for other identifier types. |

{{< tabs >}}

  {{< tab name="cURL" >}}
  ```bash
  curl -X POST "https://console.intuizi.com/api/v2/analyses/cohorts/create" \
    -H "Authorization: Bearer <YOUR_TOKEN>" \
    -H "Content-Type: application/json" \
    -H "Accept: application/json" \
    -H "Idempotency-Key: <UNIQUE_KEY>" \
    -d '{
      "name": "Loyalty program members",
      "file_uri": "s3://my-bucket/exports/devices.csv",
      "file_format": "csv",
      "identifier_type": "maid",
      "identifier_column": "maid"
    }'
  ```
  {{< /tab >}}

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

  res = requests.post(
      "https://console.intuizi.com/api/v2/analyses/cohorts/create",
      headers={
          "Authorization": "Bearer <YOUR_TOKEN>",
          "Content-Type": "application/json",
          "Accept": "application/json",
          "Idempotency-Key": "<UNIQUE_KEY>",
      },
      json={
          "name": "Loyalty program members",
          "file_uri": "s3://my-bucket/exports/devices.csv",
          "file_format": "csv",
          "identifier_type": "maid",
          "identifier_column": "maid",
      },
  )
  cohort_id = res.json()["data"][0]["id"]
  ```
  {{< /tab >}}

  {{< tab name="JavaScript" >}}
  ```javascript
  const res = await fetch(
    "https://console.intuizi.com/api/v2/analyses/cohorts/create",
    {
      method: "POST",
      headers: {
        Authorization: "Bearer <YOUR_TOKEN>",
        "Content-Type": "application/json",
        Accept: "application/json",
        "Idempotency-Key": "<UNIQUE_KEY>",
      },
      body: JSON.stringify({
        name: "Loyalty program members",
        file_uri: "s3://my-bucket/exports/devices.csv",
        file_format: "csv",
        identifier_type: "maid",
        identifier_column: "maid",
      }),
    }
  );
  const cohortId = (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/cohorts/create', [
          'name' => 'Loyalty program members',
          'file_uri' => 's3://my-bucket/exports/devices.csv',
          'file_format' => 'csv',
          'identifier_type' => 'maid',
          'identifier_column' => 'maid',
      ]);
  $cohortId = $res->json('data.0.id');
  ```
  {{< /tab >}}

{{< /tabs >}}

### Response

```json
{
  "status": "success",
  "code": 201,
  "message": "Resource created successfully.",
  "data": [
    {
      "id": 42,
      "name": "Loyalty program members",
      "status": { "id": 2, "name": "Initiating" },
      "total_eids": null,
      "project": null,
      "source": "file",
      "source_audience": null,
      "created_at": "2025-07-18 12:00:00",
      "updated_at": "2025-07-18 12:00:00"
    }
  ]
}
```

### Create from an audience

The same `POST /api/v2/analyses/cohorts/create` endpoint also builds a cohort
from a completed [audience](/api/v2/audiences) instead of a file. Send
`source` set to `audience` and an `audience_id`. The audience must be owned by
your company and be `104` Completed. A regular audience can be turned into at
most one cohort; a Lookalike Model audience can back several (one per score
range). The cohort takes its name and project from the audience, so `name` is
not required on this path.

Choose one limit for the cohort:

- **Frequency** - set `freq_limit` to `true` with `freq_min` and `freq_max`
  (and optionally `is_day_part`) to keep devices by how often they were seen.
- **Distance** - set `distance_limit` to `true` with `distance` (in meters) to
  keep devices within a radius.
- **Score** (Lookalike Model audiences only) - set `score_limit` to `true`
  with `min_score` and `max_score` to keep devices inside a model score range.
  The bounds are on the 0 to 1 score scale in steps of 0.1, the same bands the
  Audience Manager's Precision-to-Reach slider shows for the model; `min_score`
  may not exceed `max_score`. The cohort keeps the best-scored EIDs first, and
  an optional `device_limit` caps how many it keeps.
- **Device** - send none of the above for a plain device limit; add an
  optional `device_limit` to cap the number of devices kept. On a Lookalike
  Model audience the best-scored EIDs are kept first.

Like the file create, this is queued asynchronously - poll
[`GET /api/v2/analyses/cohorts/{id}`](#get-apiv2analysescohortsid) until the
cohort reaches `4` Completed.

**Auth:** bearer token + `Accept: application/json` + `Content-Type:
application/json`. Rate limit: write bucket (30 requests/min). Optional:
`Idempotency-Key` header makes this create safe to retry - see
[Idempotency](/concepts/idempotency).

#### Body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `source` | string | **Yes** | Set to `audience` to build from an audience. Defaults to `file`. |
| `audience_id` | integer | **Yes** | A `104` Completed audience owned by your company. A regular audience yields at most one cohort; a Lookalike Model audience can yield several. |
| `project_id` | integer | No | Does not change the project, since the cohort inherits the audience's project, but it is still validated: it must be a project owned by your company. |
| `freq_limit` | boolean | No | Keep devices by visit frequency. Requires `freq_min` and `freq_max`. |
| `freq_min` | integer | Conditional | Minimum visit frequency. Required when `freq_limit` is `true`. |
| `freq_max` | integer | Conditional | Maximum visit frequency. Required when `freq_limit` is `true`. |
| `is_day_part` | boolean | No | Apply the frequency window per day-part. |
| `distance_limit` | boolean | No | Keep devices within a radius. Requires `distance`. |
| `distance` | integer | Conditional | Radius in meters. Required when `distance_limit` is `true`. |
| `score_limit` | boolean | No | Lookalike Model audiences only. Keep devices inside a model score range. Requires `min_score` and `max_score`. |
| `min_score` | number | Conditional | Lower score bound, 0 to 1 in steps of 0.1, at most `max_score`. Required when `score_limit` is `true`. |
| `max_score` | number | Conditional | Upper score bound, 0 to 1 in steps of 0.1. Required when `score_limit` is `true`. |
| `device_limit` | integer | No | Cap the number of devices the cohort keeps. Applies with `score_limit` (best scores first) or when no other limit is set. |

{{< tabs >}}

  {{< tab name="cURL" >}}
  ```bash
  curl -X POST "https://console.intuizi.com/api/v2/analyses/cohorts/create" \
    -H "Authorization: Bearer <YOUR_TOKEN>" \
    -H "Content-Type: application/json" \
    -H "Accept: application/json" \
    -H "Idempotency-Key: <UNIQUE_KEY>" \
    -d '{
      "source": "audience",
      "audience_id": 8123,
      "freq_limit": true,
      "freq_min": 2,
      "freq_max": 10
    }'
  ```
  {{< /tab >}}

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

  res = requests.post(
      "https://console.intuizi.com/api/v2/analyses/cohorts/create",
      headers={
          "Authorization": "Bearer <YOUR_TOKEN>",
          "Content-Type": "application/json",
          "Accept": "application/json",
          "Idempotency-Key": "<UNIQUE_KEY>",
      },
      json={
          "source": "audience",
          "audience_id": 8123,
          "freq_limit": True,
          "freq_min": 2,
          "freq_max": 10,
      },
  )
  cohort_id = res.json()["data"][0]["id"]
  ```
  {{< /tab >}}

  {{< tab name="JavaScript" >}}
  ```javascript
  const res = await fetch(
    "https://console.intuizi.com/api/v2/analyses/cohorts/create",
    {
      method: "POST",
      headers: {
        Authorization: "Bearer <YOUR_TOKEN>",
        "Content-Type": "application/json",
        Accept: "application/json",
        "Idempotency-Key": "<UNIQUE_KEY>",
      },
      body: JSON.stringify({
        source: "audience",
        audience_id: 8123,
        freq_limit: true,
        freq_min: 2,
        freq_max: 10,
      }),
    }
  );
  const cohortId = (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/cohorts/create', [
          'source' => 'audience',
          'audience_id' => 8123,
          'freq_limit' => true,
          'freq_min' => 2,
          'freq_max' => 10,
      ]);
  $cohortId = $res->json('data.0.id');
  ```
  {{< /tab >}}

{{< /tabs >}}

The response is the same shape as the file create above, with `source` set
to `audience` and `source_audience` naming the audience the cohort is built
from.

## Preview Cohort File {#post-apiv2analysescohortspreview}

`POST /api/v2/analyses/cohorts/preview`

Reads only a bounded prefix (about 64 KB) of a cohort source file and returns
the column headers, up to 20 sample rows keyed by column name, and the number
of complete rows found in the prefix (not the file total). Use it to confirm
`identifier_column` and `metadata_columns` before creating the cohort, instead
of finding out after the import fails.

Takes the same source the create takes: `file_uri` for a file in your own
cloud, or `upload_reference` for a file you uploaded - exactly one of the two.
Previewing does not consume an `upload_reference`.

**Auth:** bearer token + `Accept: application/json` + `Content-Type:
application/json`. Rate limit: write bucket (30 requests/min).

### Body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `file_uri` | string | One of | `s3://bucket/path` or `gs://bucket/path` - a file, or a folder of files, readable by Intuizi's processing account. The preview reads the file at the URI whatever its name, and reads a folder only when the URI ends in `/` or no file is there, taking the header from its first data file (hidden `_`/`.`-prefixed files such as Spark's `_SUCCESS` are skipped). The create goes by suffix instead, so a single file must end in `.csv`, `.gz` or `.parquet` to import as a file. Send either this or `upload_reference`, not both. |
| `upload_reference` | string | One of | The `upload_reference` from [Create an Upload](/api/v2/uploads#post-apiv2uploadscreate) (purpose `cohort`), after the file was `PUT`. Send either this or `file_uri`, not both. |
| `file_format` | string | No | `csv` (default) or `gzip`. Parquet files cannot be previewed. |

{{< tabs >}}

  {{< tab name="cURL" >}}
  ```bash
  curl -X POST "https://console.intuizi.com/api/v2/analyses/cohorts/preview" \
    -H "Authorization: Bearer <YOUR_TOKEN>" \
    -H "Content-Type: application/json" \
    -H "Accept: application/json" \
    -d '{
      "upload_reference": "upl_01k0p3v9example"
    }'
  ```
  {{< /tab >}}

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

  res = requests.post(
      "https://console.intuizi.com/api/v2/analyses/cohorts/preview",
      headers={
          "Authorization": "Bearer <YOUR_TOKEN>",
          "Content-Type": "application/json",
          "Accept": "application/json",
      },
      json={"upload_reference": "upl_01k0p3v9example"},
  )
  columns = res.json()["data"][0]["columns"]
  ```
  {{< /tab >}}

  {{< tab name="JavaScript" >}}
  ```javascript
  const res = await fetch(
    "https://console.intuizi.com/api/v2/analyses/cohorts/preview",
    {
      method: "POST",
      headers: {
        Authorization: "Bearer <YOUR_TOKEN>",
        "Content-Type": "application/json",
        Accept: "application/json",
      },
      body: JSON.stringify({ upload_reference: "upl_01k0p3v9example" }),
    }
  );
  const columns = (await res.json()).data[0].columns;
  ```
  {{< /tab >}}

  {{< tab name="PHP" >}}
  ```php
  $res = Http::withToken('<YOUR_TOKEN>')->acceptJson()
      ->post('https://console.intuizi.com/api/v2/analyses/cohorts/preview', [
          'upload_reference' => 'upl_01k0p3v9example',
      ]);
  $columns = $res->json('data.0.columns');
  ```
  {{< /tab >}}

{{< /tabs >}}

### Response

```json
{
  "status": "success",
  "code": 200,
  "message": "Resource fetched successfully.",
  "data": [
    {
      "columns": ["maid", "segment"],
      "samples": [
        { "maid": "38f2a9c1-0000-4e6b-9c7d-000000000000", "segment": "gold" }
      ],
      "sample_rows": 214
    }
  ]
}
```

## List Cohorts {#get-apiv2analysescohortsindex}

`GET /api/v2/analyses/cohorts/index`

Lists the cohorts owned by your 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 cohort name (case-insensitive contains). |

### Response

```json
{
  "status": "success",
  "code": 200,
  "message": "Resources fetched successfully.",
  "data": {
    "items": [
      {
        "id": 42,
        "name": "Loyalty program members",
        "status": { "id": 4, "name": "Completed" },
        "total_eids": 184233,
        "project": null,
        "source": "file",
        "source_audience": null,
        "created_at": "2025-07-18 12:00:00",
        "updated_at": "2025-07-18 12:41:07"
      }
    ],
    "pagination": { "current_page": 1, "per_page": 25, "total": 1, "last_page": 1 }
  }
}
```

## Delete Cohort {#post-apiv2analysescohortsdelete-by-id}

`POST /api/v2/analyses/cohorts/delete-by-id`

Deletes one cohort owned by your company. The cohort's imported data is
deleted with it unless it was built from a regular audience that still exists,
so a file-imported or Lookalike Model cohort loses its data. Once the data is gone,
an audience whose only dataset is this cohort can no longer be activated, and
a schedule whose audience uses the cohort fails from its next cycle. Audiences
that combine the cohort with other datasets keep their built results.

**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 cohort id to delete. Must be owned by your company. |

### Response

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

## Get Cohort {#get-apiv2analysescohortsid}

`GET /api/v2/analyses/cohorts/{id}`

Fetches one cohort owned by your company, including its lifecycle status. Poll
this after a create until the status reaches `4` Completed - then the cohort id
is valid as `datasets[].cohort_id` on the
[Create Audience]({{< relref "/api/v2/audiences" >}}#post-apiv2analysesaudiencescreate)
surface - or `5` Not Available, which means the import failed.

**Auth:** bearer token + `Accept: application/json`. Rate limit: read bucket
(120 requests/min).

### Path parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | integer | Yes | The cohort id to fetch. |

The cohort statuses:

| Status | Name | Meaning |
| --- | --- | --- |
| `1` | Uploading | The cohort row exists; the import has not been queued yet. |
| `2` | Initiating | The import is queued for processing. |
| `3` | Processing | The file is being imported and matched. |
| `4` | Completed | The cohort is ready to use in an audience. |
| `5` | Not Available | The import failed. The cohort cannot be used in an audience. |

`4` and `5` are final, so stop polling at either. A
[`cohort.failed`](/api/v2/webhooks#cohort-events) webhook is sent at `5`. A
cohort that failed before failures were reported as `5` can still read the
error code it failed with, which the API names `Unknown`. Treat that as a
failed import too. What to do after a failed import depends on the source:

- `file_uri`: fix the file, then create the cohort again.
- `upload_reference`: the failed create used the reference up, so upload the
  file again for a new reference.
- `audience_id` with a regular audience: the failed cohort still counts as the
  audience's one cohort, so
  [delete it](#post-apiv2analysescohortsdelete-by-id)
  before creating from that audience again. A Lookalike Model audience needs
  no delete.

### Response

`source` says how the cohort was created, in the words the create call takes:
`file` (a file in your cloud or an upload), `audience` (built from an
audience, including a Lookalike Model), or `pixel` (built in the console from
a pixel campaign). It is `null` for cohorts whose origin was not recorded,
such as older cohorts.

`source_audience` names the audience a `source: "audience"` cohort was built
from, as `{ "id", "name" }`. Follow it with
[Get Audience]({{< relref "/api/v2/audiences" >}}#get-apiv2analysesaudiencesid) -
for a Lookalike Model, that read's own `source_audience` names the seed. It is
`null` for file and pixel cohorts, for cohorts whose origin was not recorded,
and when the audience has been deleted or does not belong to your company.

```json
{
  "status": "success",
  "code": 200,
  "message": "Resource fetched successfully.",
  "data": {
    "id": 42,
    "name": "Loyalty program members",
    "status": { "id": 4, "name": "Completed" },
    "total_eids": 184233,
    "project": null,
    "source": "file",
    "source_audience": null,
    "created_at": "2025-07-18 12:00:00",
    "updated_at": "2025-07-18 12:41:07"
  }
}
```
