# Activate an Audience


Activate a completed audience to deliver it to a destination - an
[endpoint connection](/concepts/endpoint-connections) at a partner. Activation is
asynchronous: you submit it, poll it to `Completed`, then read
the result location from its datastreams.

## Prerequisites

- A bearer token ([Authentication](/getting-started/authentication)).
- An audience in status `104` Completed (see
  [Create an Audience](/guides/create-an-audience)) with at least **500 unique
  devices** - the activation eligibility engine also checks Affinity device
  coverage, so an audience can fail activation for more than just the device
  floor (see [Common rejections](#common-rejections) below). Check before
  activating: the [audience read](/guides/read-an-audience) returns
  `results_count`, `is_activation_allowed`, and `eligibility` (reasons, notices
  + metrics). A notice does not block: `audience_expired` means the data window
  ended more than 90 days ago, so a **MAID** or **IP** pricing model is rejected
  while EID / SCID / HEM deliveries go through.
- An endpoint connection, a pricing model, and the datastreams you want - read
  them with
  [Working with Reference Data](/guides/working-with-reference-data#shared-and-activation-reference-reads).

## Pick the destination

You assemble three things from reference data:

1. **The endpoint connection** (`endpoint_connection_id`) - your company's saved
   link to a partner. This is the anchor: the platform resolves the partner, the
   available pricing, and the datastreams **server-side** from it.
2. **The pricing model** (`pricing_model_id`) - one of that partner's public
   pricing options.
3. **The datastreams** - the output streams you want from the partner. Each one
   you want delivered is sent as `{ "id": <id>, "status": true }`; a stream
   without `status` is not activated.

{{< callout type="warning" >}}
You cannot supply the partner identity yourself. The activation request
**prohibits** `partner_id`, `partner_name`, and `pricing_model` - sending any of
them returns `422`. The partner and pricing are always derived from the endpoint
connection, so a caller can never inject a different identity.
{{< /callout >}}

## Credentials

By default the activation authenticates to the partner with the **stored
credentials** on the endpoint connection. You can also supply credentials
**per call** in the request body.

- **Precedence:** caller-supplied credentials, when present, **override** the
  connection's stored credentials. When absent, the stored credentials are used.
- **Required somewhere:** if neither caller credentials nor stored credentials
  exist, the activation is rejected with `422`.
- **Write-only:** credentials are never returned in any create, read, or list
  response, and never logged.

{{< callout type="error" >}}
Caller-supplied credentials are secret material. Send them only over HTTPS, never
log them on your side, and never put them in a URL or query string. They are
accepted in the request body, used to authenticate the delivery, and never
returned by the API. See
[Endpoint Connections & Partners](/concepts/endpoint-connections).
{{< /callout >}}

## Partner inputs

Most partners also take **account-detail inputs** (an account id, a seat id, a
folder prefix, and so on). Their definitions come from the endpoint connection -
[Get Endpoint Connections](/api/v2/common/#get-apiv2analysesreferencecommonendpoint-connections)
returns `partner.inputs`: each input's `name`, `type` and whether it is
`required`, in a fixed order.

- Send the values as `audience_inputs` (top level) and/or `datastreams[].inputs`
  (per stream), **index-matched to that definitions order**. Per-stream values
  win; inputs flagged `required` must be supplied for every enabled datastream.
- A `file`-type input is the **service-account JSON key** (GCP-style
  destinations). Send its decoded JSON as `datastreams[].service_account` -
  it is only accepted for partners that define a file input.

For the step-by-step version of this - reading the connection, its pricing
models, its datastreams and its input definitions, then mapping your values onto
them - see [Deliver to a Partner Endpoint](/guides/deliver-to-a-partner-endpoint).

## Optional: preview the frequency filter

If the audience was built with the Frequency analysis you can export only the
devices seen on a given number of distinct days - the Audience Manager's
"Limit by Frequency" range. Before you activate, ask
[Preview Activation](/api/v2/activations#get-apiv2analysesactivationspreview)
what a range would export:

```text
GET /api/v2/analyses/activations/preview?audience_id=88&freq_min=2&freq_max=5
```

It returns `filtered_count` (exact, the same figure the Audience Manager shows
as Limit Audience), the histogram and its `frequency_bounds`, the read-only
`recency` window and a `filter_hash`. Nothing is created or billed, so preview
as many ranges as you like. Then send the same `freq_min` / `freq_max` with
`freq_limit: true` and that `filter_hash` in the create body below: the server
recomputes the hash and refuses a request whose range or audience data no longer
matches the preview. Recency is fixed by the audience definition - to count
against another date window, build a new audience.

## Request

`POST /api/v2/analyses/activations/create`

{{< tabs >}}

  {{< tab name="cURL" >}}
  ```bash
  curl -X POST "https://console.intuizi.com/api/v2/analyses/activations/create" \
    -H "Authorization: Bearer <YOUR_TOKEN>" \
    -H "Content-Type: application/json" \
    -H "Accept: application/json" \
    -H "Idempotency-Key: <UNIQUE_KEY>" \
    -d '{
      "audience_id": 88,
      "endpoint_connection_id": 17,
      "pricing_model_id": 3,
      "datastreams": [
        { "id": 5, "status": true },
        { "id": 6, "status": true }
      ],
      "credentials": {
        "api_key": "<PARTNER_API_KEY>",
        "api_secret": "<PARTNER_API_SECRET>"
      }
    }'
  ```
  {{< /tab >}}

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

  res = requests.post(
      "https://console.intuizi.com/api/v2/analyses/activations/create",
      headers={
          "Authorization": "Bearer <YOUR_TOKEN>",
          "Accept": "application/json",
          "Idempotency-Key": "<UNIQUE_KEY>",
      },
      json={
          "audience_id": 88,
          "endpoint_connection_id": 17,
          "pricing_model_id": 3,
          "datastreams": [{"id": 5, "status": True}, {"id": 6, "status": True}],
          # Omit "credentials" to use the connection's stored credentials.
          "credentials": {
              "api_key": "<PARTNER_API_KEY>",
              "api_secret": "<PARTNER_API_SECRET>",
          },
      },
  )
  activation_id = res.json()["data"][0]["id"]
  ```
  {{< /tab >}}

  {{< tab name="JavaScript" >}}
  ```javascript
  const res = await fetch(
    "https://console.intuizi.com/api/v2/analyses/activations/create",
    {
      method: "POST",
      headers: {
        "Authorization": "Bearer <YOUR_TOKEN>",
        "Content-Type": "application/json",
        "Accept": "application/json",
        "Idempotency-Key": "<UNIQUE_KEY>",
      },
      body: JSON.stringify({
        audience_id: 88,
        endpoint_connection_id: 17,
        pricing_model_id: 3,
        datastreams: [
          { id: 5, status: true },
          { id: 6, status: true },
        ],
        // Omit "credentials" to use the connection's stored credentials.
        credentials: {
          api_key: "<PARTNER_API_KEY>",
          api_secret: "<PARTNER_API_SECRET>",
        },
      }),
    }
  );
  const activationId = (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/activations/create', [
          'audience_id' => 88,
          'endpoint_connection_id' => 17,
          'pricing_model_id' => 3,
          'datastreams' => [
              ['id' => 5, 'status' => true],
              ['id' => 6, 'status' => true],
          ],
          // Omit 'credentials' to use the connection's stored credentials.
          'credentials' => [
              'api_key' => '<PARTNER_API_KEY>',
              'api_secret' => '<PARTNER_API_SECRET>',
          ],
      ]);
  $activationId = $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 launching a duplicate
activation; the same key with a different body returns `409`.

{{< /callout >}}

The exact, field-level contract - and the credentials field marked write-only /
sensitive - is in the [API Reference]({{< relref "/api/v2/activations" >}}). This guide is the
narrative; the reference is the contract.

## Common rejections

| Status | When |
| --- | --- |
| `404` | `audience_id` or `endpoint_connection_id` was not found. |
| `422` | No credentials available (neither caller-supplied nor stored). |
| `422` | The audience fails an eligibility gate (`is_activation_allowed` is `false`): fewer than 500 devices or Affinity device coverage below 2x - the message is `eligibility.reasons[0].message`. |
| `422` | The pricing model delivers an identifier an eligibility notice blocks: a MAID or IP pricing model on an audience whose data window ended more than 90 days ago (`eligibility.notices[].code = audience_expired`). Pick an EID / SCID / HEM pricing model or rebuild the audience with a recent window. |
| `422` | A prohibited identity field (`partner_id`, `partner_name`, `pricing_model`) was sent. |
| `422` | `freq_min` / `freq_max` were sent without an explicit `freq_limit`, or the `filter_hash` does not match the previewed range on the audience as it is stored now. |

## Poll until Completed

Activation runs asynchronously (see [The Async Model](/concepts/async-model)). The
create response gives you the new activation id; poll it until its lifecycle
status reaches `104` Completed.

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

- `100`-`103`, `108`, `109` - still processing. Poll again shortly.
- `105` DataStreaming - results are being delivered to the destination. It
  comes before `104`, so poll again.
- `104` Completed - done, and the results are available.
- `107` Additional Info - the export stopped and will not continue. Stop
  polling. Audience Manager shows the reason on the activation.
- `4xx` - an error state. Stop polling and inspect the response.

Instead of polling, a registered [webhook](/concepts/webhooks) pushes the
`activation.completed` event to your server the moment the activation
completes, or `activation.failed` the moment it stops at `107` or a `4xx`
state, with the same activation object as `data`. Keep a low-frequency poll
as the fallback for a delivery that exhausts its retries.

## Read the results

Once the activation reaches `104` (Completed), read the result location from the
`datastreams` array in the response. `105` (DataStreaming) happens **before**
`104` - it means the delivery is still in progress. Each datastream exposes a
result URI:

```json
{
  "status": "success",
  "code": 200,
  "message": "...",
  "data": [
    {
      "id": 91,
      "status": { "id": 104, "name": "Completed" },
      "datastreams": [ { "name": "standard_delivery", "status": "success", "results": { "uri": "s3://..." } } ]
    }
  ]
}
```

Pull `data[0].datastreams[].results.uri` to fetch the output. See
[Read an Activation](/guides/read-an-activation) for the full read response.

{{< callout type="error" >}}
Activation delivers data to an external partner. Treat it as a live, billable,
non-reversible delivery: once an activation is created it is queued for streaming
to the destination. Activate only completed audiences, against the connection you
intend, with the pricing model you intend.
{{< /callout >}}

## Reference

- Concept: [Endpoint Connections & Partners](/concepts/endpoint-connections)
- Concept: [Audiences vs Activations](/concepts/audiences-vs-activations)
- Per-partner walkthrough: [Deliver to a Partner Endpoint](/guides/deliver-to-a-partner-endpoint)
- Reference data: [Working with Reference Data](/guides/working-with-reference-data#shared-and-activation-reference-reads)
- Polling and retries: [Polling and Rate Limits](/guides/polling-and-rate-limits)
- Activating a lookalike: [Build a Lookalike Model](/guides/build-a-lookalike-model) (1,000-device floor)
- Field-level contract: [API Reference - Activations]({{< relref "/api/v2/activations" >}})
