# Deliver to a Partner Endpoint


[Activate an Audience](/guides/activate-an-audience) covers the shape of an
activation. This guide is the per-partner version of that journey: taking one
completed audience and landing it at one specific destination, with the exact
ids, streams and account details that destination expects.

The pattern is always the same, whichever partner you deliver to. You read the
connection, read what its partner offers, fill in that partner's inputs, and
send one activation. Nothing about the partner is guessed or typed from memory.

{{< callout type="info" >}}
This page never lists partners, their pricing models, their datastreams, or
their input fields. That catalog is specific to your account and changes over
time, so the reference reads below are the only accurate source. Code that
hardcodes a partner name, a pricing model id, or an input order breaks the first
time the catalog changes.
{{< /callout >}}

## Before you start

- A bearer token ([Authentication](/getting-started/authentication)).
- An audience in status `104` Completed that passes every activation
  eligibility gate (device-count floor, Affinity SCID coverage, retention -
  see the gate table in [Read an Audience](/guides/read-an-audience)). The
  [audience read](/guides/read-an-audience) returns an explicit
  `is_activation_allowed` boolean and, when it is `false`,
  `eligibility.reasons[]` explains which gate blocked it - check it before you
  build the request.
- An **endpoint connection** for the destination you want.

{{< callout type="warning" >}}
Endpoint connections are created in the console, under **Audience Manager** -
not through the API. The API reads the connections that already exist for your
account and activates into them. If the destination you need is not in the list,
or you want a new partner enabled, your Account Manager sets it up; some
destinations require additional permissions which need to be approved by your
Account Manager.
{{< /callout >}}

## Step 1 - Find the endpoint connection

Everything else in this journey hangs off the connection. It is the anchor of
the activation: the platform resolves the partner, the pricing options and the
datastreams from it, server-side.

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

Each entry carries the connection id, its name, and the partner behind it -
including that partner's **input definitions**:

```json
{
  "id": 12,
  "name": "Acme Production",
  "partner": {
    "id": 3,
    "name": "Acme DSP",
    "inputs": [
      { "name": "Account ID", "type": "text", "required": true, "description": "Partner account id", "placeholder": "Account ID", "default_value": null },
      { "name": "Account Credentials", "type": "file", "required": true, "description": "Service Account .json file", "placeholder": "Service Account .json file", "default_value": null }
    ]
  }
}
```

Keep three things from this response: the connection `id` (for
`endpoint_connection_id`), the `partner.id` (the next two reads need it), and
the `partner.inputs` array **in the order it arrives** - that order is the
contract for Step 4.

Use `search` to narrow a long list by connection name. The full field contract
is in
[Get Endpoint Connections](/api/v2/common/#get-apiv2analysesreferencecommonendpoint-connections);
the read never returns stored credentials or stored input values.

## Step 2 - Read that partner's pricing models

Pricing is per partner, so this read takes the `partner_id` you just kept:

```bash
curl "https://console.intuizi.com/api/v2/analyses/reference/common/pricing-models?partner_id=3" \
  -H "Authorization: Bearer <YOUR_TOKEN>" \
  -H "Accept: application/json"
```

```json
{ "data": [ { "id": 9, "name": "CPM", "price": "2.50" } ] }
```

Pick one `id` - that is your `pricing_model_id`. See
[Get Pricing Models](/api/v2/common/#get-apiv2analysesreferencecommonpricing-models).

{{< callout type="warning" >}}
You supply the pricing model **id**, never a pricing model name. The activation
request prohibits `partner_id`, `partner_name` and `pricing_model`; sending any
of them returns `422`. Identity always comes from the connection.
{{< /callout >}}

## Step 3 - Read that partner's datastreams

Datastreams are the outputs the partner exposes. Same `partner_id`:

```bash
curl "https://console.intuizi.com/api/v2/analyses/reference/common/datastreams?partner_id=3" \
  -H "Authorization: Bearer <YOUR_TOKEN>" \
  -H "Accept: application/json"
```

```json
{ "data": [ { "id": 7, "name": "Match File" } ] }
```

Keep the id of every stream you want delivered. See
[Get Datastreams](/api/v2/common/#get-apiv2analysesreferencecommondatastreams).

A stream is only delivered when you enable it explicitly:

```json
"datastreams": [ { "id": 7, "status": true } ]
```

An entry without `"status": true` is not activated, so an activation whose
streams are all left disabled has nothing to deliver.

## Step 4 - Map the partner's account inputs

Most partners need account details alongside the audience - an account id, a
seat id, a folder prefix, a service-account key. Those are the `partner.inputs`
definitions from Step 1. Each definition carries a `name`, a `type` (`text` or
`file`), a `required` flag and UI hints, and the array order is meaningful.

You send **values**, not names, and they are matched to the definitions **by
index**:

- `audience_inputs` - one array for the whole activation.
- `datastreams[].inputs` - one array per stream, for streams that need
  different values. A per-stream value wins over the top-level one at the same
  index.
- A `text` definition flagged `required` must resolve to a value for **every
  enabled datastream**, from the per-stream array, the top-level array, or the
  definition's `default_value`. One left empty is rejected with a `422` naming
  that input. A `default_value` passes this check but is not copied into the
  delivery, so send the value explicitly. A `file` definition travels
  separately (below).

For the example connection in Step 1, the mapping is:

| Index | `name` | `type` | `required` | Where its value goes |
| --- | --- | --- | --- | --- |
| 0 | Account ID | `text` | Yes | `audience_inputs[0]` or `datastreams[].inputs[0]` |
| 1 | Account Credentials | `file` | Yes | `datastreams[].service_account` |

Read your own connection's definitions the same way: walk the array, note the
index of each one, and place your values at those indexes. Keep positions
aligned by sending `null` at an index whose value travels elsewhere (a `file`
input, below) rather than shortening the array.

### Service-account keys

A `file`-type definition is the destination's **service-account JSON key**
(GCP-style destinations). It does not travel in the input arrays. Send the
decoded JSON object as `datastreams[].service_account` on each enabled stream.
It is accepted only for partners that define a `file` input - sending it to a
partner that does not is rejected with `422`.

## Step 5 - Decide how the delivery authenticates

By default the delivery authenticates with the **stored credentials** on the
endpoint connection, and you send no `credentials` at all. Supply the
`credentials` object only when you hold the destination credentials yourself:
caller-supplied credentials override the stored ones for that one delivery, and
if neither exists the activation is rejected with `422`.

{{< callout type="error" >}}
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 >}}

## Step 6 - Send the activation

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

One request carries the audience, the connection, the pricing model, the
enabled streams and the partner inputs.

{{< 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": 12,
      "pricing_model_id": 9,
      "description": "Q1 retail delivery",
      "audience_inputs": ["ACME-4471", null],
      "datastreams": [
        {
          "id": 7,
          "status": true,
          "service_account": { "type": "service_account", "project_id": "acme-delivery" }
        }
      ]
    }'
  ```
  {{< /tab >}}

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

  res = requests.post(
      "https://console.intuizi.com/api/v2/analyses/activations/create",
      headers={
          "Authorization": "Bearer <YOUR_TOKEN>",
          "Content-Type": "application/json",
          "Accept": "application/json",
          "Idempotency-Key": "<UNIQUE_KEY>",
      },
      json={
          "audience_id": 88,
          "endpoint_connection_id": 12,
          "pricing_model_id": 9,
          "description": "Q1 retail delivery",
          # Index 0 is the "Account ID" text input; index 1 is the file input,
          # whose value travels as service_account below.
          "audience_inputs": ["ACME-4471", None],
          "datastreams": [
              {
                  "id": 7,
                  "status": True,
                  "service_account": {"type": "service_account", "project_id": "acme-delivery"},
              }
          ],
      },
  )
  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: 12,
        pricing_model_id: 9,
        description: "Q1 retail delivery",
        // Index 0 is the "Account ID" text input; index 1 is the file input,
        // whose value travels as service_account below.
        audience_inputs: ["ACME-4471", null],
        datastreams: [
          {
            id: 7,
            status: true,
            service_account: { type: "service_account", project_id: "acme-delivery" },
          },
        ],
      }),
    }
  );
  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' => 12,
          'pricing_model_id' => 9,
          'description' => 'Q1 retail delivery',
          // Index 0 is the "Account ID" text input; index 1 is the file input,
          // whose value travels as service_account below.
          'audience_inputs' => ['ACME-4471', null],
          'datastreams' => [
              [
                  'id' => 7,
                  'status' => true,
                  'service_account' => ['type' => 'service_account', 'project_id' => 'acme-delivery'],
              ],
          ],
      ]);
  $activationId = $res->json('data.0.id');
  ```
  {{< /tab >}}

{{< /tabs >}}

The response returns the new activation with its id and an opening lifecycle
status. Field by field, the body contract is in
[API Reference - Activations](/api/v2/activations/#post-apiv2analysesactivationscreate),
including the optional delivery tuning fields (`limit`, `compression`,
frequency and distance caps, `project_id`, `notification`).

{{< callout type="info" >}}
`Idempotency-Key` is optional and you generate it yourself - one unique string
per logical delivery. Resend the same key only when retrying that exact request:
the retry replays the original response instead of launching a second delivery.
See [Idempotency](/concepts/idempotency).
{{< /callout >}}

## Step 7 - Poll the delivery

Delivery is asynchronous (see [The Async Model](/concepts/async-model)). Poll
the activation until its lifecycle status reaches `104` Completed:

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

- `100`-`103`, `108`, `109` - still processing. Poll again shortly.
- `105` DataStreaming - the delivery to the destination is in flight. `105`
  happens **before** `104`, so keep polling.
- `104` Completed - the only terminal success state.
- `107` Additional Info - the export stopped and will not continue. Stop
  polling. Audience Manager shows the reason on the activation.
- `4xx` lifecycle ids - an error state. Stop polling and inspect the resource.

Each entry in the `datastreams` array of the read reports that stream's `name`,
its `status`, a `results` object (carrying a `uri` when an output file is
available) and an `error` string only when that stream failed - so a partial
delivery is visible per stream, not just per activation. Data delivered into the
destination configured on the connection lands there directly; the API does not
proxy it. See [Read an Activation](/guides/read-an-activation) for the full
response, and [Polling and Rate Limits](/guides/polling-and-rate-limits) for
sensible intervals.

## Delivering again

Activations are point-in-time. To deliver a refreshed audience, build the new
audience and create a **new** activation against the same connection with a new
`Idempotency-Key`; to deliver the same audience to a second destination, repeat
Steps 1-6 with that destination's connection id and its own partner inputs.

{{< callout type="error" >}}
An activation is a live, billable, non-reversible delivery to an external
destination. Activate only completed audiences, against the connection you
intend, with the pricing model you intend.
{{< /callout >}}

## Checklist

- Connection id read from `endpoint-connections`, not hardcoded.
- `pricing_model_id` and every datastream id read for that connection's
  `partner.id`.
- Each stream you want delivered carries `"status": true`.
- Every `required` input definition resolves to a value for every enabled
  stream, index-matched to `partner.inputs`.
- `service_account` sent only when the partner defines a `file` input.
- Credentials either stored on the connection or supplied in the body.
- No `partner_id`, `partner_name` or `pricing_model` in the request.

Rejection shapes and status codes are documented once, on
[Errors](/concepts/errors).

## Reference

- Concept: [Endpoint Connections & Partners](/concepts/endpoint-connections)
- Journey: [Activate an Audience](/guides/activate-an-audience)
- Reference reads: [Working with Reference Data](/guides/working-with-reference-data#shared-and-activation-reference-reads)
- Field-level contract: [API Reference - Activations](/api/v2/activations)
- Shared catalogs: [API Reference - Common](/api/v2/common)
- Driving this flow from an agent:
  [Automate with an AI Agent](/guides/automate-with-an-ai-agent)
