> ## Documentation Index
> Fetch the complete documentation index at: https://docs.narrative.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Delivering Third-Party Audiences to The Trade Desk via the API

> Upload an audience to Narrative and deliver it to The Trade Desk's marketplace over the API

This guide shows you how to build an unattended integration that uploads an audience to Narrative and delivers it to The Trade Desk (TTD) marketplace, where buyers you choose can purchase it. A person signs in once to create an API key, and the integration uses that key from then on.

Third-party audiences are sold from a taxonomy that TTD reviews before anything activates. This guide attaches your data to that taxonomy and delivers it. To build and price the taxonomy itself, see [Managing your taxonomy via the API](/guides/connector-apis/the-trade-desk/managing-taxonomy-api). To deliver into one advertiser's own seat instead, see [First-party delivery via the API](/guides/connector-apis/the-trade-desk/first-party-delivery-api).

Before you start, complete the [prerequisites](/guides/connector-apis/the-trade-desk#prerequisites) and [set up API access](/guides/connector-apis/the-trade-desk#set-up-api-access).

## 1. Create the dataset

Your schema decides whether The Trade Desk will accept the audience, so settle it before you upload anything.

The dataset must carry at least one identifier the connector recognizes, and each one is an object with a `value` property inside it. See [Supported identifiers](/reference/connectors/the-trade-desk#supported-identifiers) for the full list.

```bash theme={null}
curl -X POST https://api.narrative.io/datasets \
  -H "Authorization: Bearer $NIO_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Q3 In-Market Auto",
    "description": "Audience for The Trade Desk",
    "write_mode": "overwrite",
    "schema": {
      "file_config": { "type": "json" },
      "type": "object",
      "properties": {
        "android_advertising_id": {
          "type": "object",
          "display_name": "Android Advertising ID",
          "properties": {
            "value": { "type": "string", "display_name": "Value" }
          }
        }
      }
    }
  }'
```

`write_mode` is `overwrite` or `append`. `file_config.type` is `json` for JSON Lines, `parquet`, or `flat` for CSV. Both fields are required.

The dataset comes back `pending`. Record its ID.

<Note>
  You do not need to supply UID2. The connector generates UID2 tokens from hashed email addresses automatically.
</Note>

## 2. Activate the dataset

```bash theme={null}
curl -X POST https://api.narrative.io/datasets/{dataset_id}/activate \
  -H "Authorization: Bearer $NIO_API_TOKEN"
```

Activation locks the schema. You cannot change it afterward, so activate only once the shape is settled.

## 3. Upload your file

Request an upload URL, then send the file straight to storage:

```bash theme={null}
curl -X POST https://api.narrative.io/uploads/audiences/q3-auto.json \
  -H "Authorization: Bearer $NIO_API_TOKEN"
# → {"path": "a1b2c3....json", "url": "https://...", "expiry": "..."}

curl -X PUT "<url from the response>" --upload-file ./q3-auto.json
```

The upload URL is valid for 30 minutes and carries its own signature, so send no authorization header with the `PUT`.

<Note>
  A signed-URL upload supports a single file of up to 3 GB. For larger files, or for files that another system delivers on a schedule, write them to a managed bucket instead: see [Ingesting Files from a Managed S3 Bucket](/guides/ingestion/ingesting-files-from-a-managed-s3-bucket). That replaces this step and the next one.
</Note>

<Warning>
  Keep the `path` from the response. Narrative assigns its own storage path, which will not match the one you requested, and the next step needs Narrative's path rather than yours.
</Warning>

## 4. Ingest the file

```bash theme={null}
curl -X POST https://api.narrative.io/datasets/{dataset_id}/upload \
  -H "Authorization: Bearer $NIO_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"source_file": "a1b2c3....json"}'
```

Ingestion runs in the background. Watch the record count on the dataset to know when it has finished:

```bash theme={null}
curl -s https://api.narrative.io/datasets/{dataset_id} \
  -H "Authorization: Bearer $NIO_API_TOKEN"
# → .stats.active_dataset_stored_records
```

The count moves from zero to your row count, typically within a couple of minutes.

## 5. Confirm The Trade Desk accepts the dataset

```bash theme={null}
curl -s https://api.narrative.io/datasets/{dataset_id}/interfaces \
  -H "Authorization: Bearer $NIO_API_TOKEN"
```

Look for The Trade Desk (`"app_id": 11`) in the accepted list, under the interface your delivery model uses: `first_party` for first-party, `third_party_v2` for third-party. See [Delivery interfaces](/reference/connectors/the-trade-desk#delivery-interfaces) for what each interface does.

If The Trade Desk appears under rejected instead, the response tells you what is missing:

```json theme={null}
{
  "app_id": 11,
  "interface_id": "third_party_v2",
  "details": {
    "valid": false,
    "errors": { "required": "required property 'android_advertising_id' not found" }
  }
}
```

That is a schema problem. Because the schema is locked at activation, fixing it means creating a new dataset.

This endpoint returns `404` until the dataset is activated.

### Mapping identifiers

Mapping your identifier column to a [Rosetta Stone](/concepts/rosetta-stone/overview) attribute is **not required** for The Trade Desk to accept the dataset. Acceptance is decided by your schema alone. Map anyway: normalized attributes make the dataset usable across other destinations and Narrative features.

Mapping needs a data sample first:

```bash theme={null}
curl -X POST https://api.narrative.io/datasets/{dataset_id}/request-sample \
  -H "Authorization: Bearer $NIO_API_TOKEN"
# poll https://api.narrative.io/jobs?dataset_id={dataset_id}
# until the datasets_sample job completes (roughly 8 minutes)

curl -X POST https://api.narrative.io/mappings/companies/{company_id} \
  -H "Authorization: Bearer $NIO_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "dataset_id": 12345,
    "attribute_id": 70,
    "mapping": {
      "type": "object_mapping",
      "property_mappings": [
        { "path": "value", "expression": "android_advertising_id.\"value\"" }
      ]
    }
  }'
```

`value` is a [reserved keyword](/nql/general/reserved-keywords) in mapping expressions, so quote it.

Attribute IDs for the common mobile identifiers: `mobile_id_unique_identifier` is 68, `apple_idfa` is 69, `android_advertising_id` is 70.

## 6. Find your connector profile

Your profile holds your Brand ID and backs every delivery.

```bash theme={null}
curl -s "https://api.narrative.io/installations?app_categories=destination_connector" \
  -H "Authorization: Bearer $NIO_API_TOKEN"
# find the record with "app_id": 11 and note its "id"

curl -s https://api.narrative.io/installations/{installation_id}/profiles \
  -H "Authorization: Bearer $NIO_API_TOKEN"
# → [{"id": "f692fb18-...", "status": "enabled", ...}]
```

Record the profile ID. Its status must be `enabled`. If you have no profile yet, create one in the Narrative UI under **Objects → Installed Apps → Trade Desk Connector** and enter your Brand ID there.

## 7. Attach the dataset to a taxonomy element

Every audience you sell is a buyable [taxonomy element](/reference/glossary#taxonomy-element) with your dataset in its `datasets` list.

**For a new audience**, create the element with your dataset ID in `datasets`, as shown in [Create an audience](/guides/connector-apis/the-trade-desk/managing-taxonomy-api#3-create-an-audience).

**For an element that already exists**, send it back with your dataset added. `PUT` replaces the whole element, so send every field, including the rates it already has:

```bash theme={null}
curl -X PUT https://thetradedesk.narrativeconnectors.com/taxonomies/elements/{element_id} \
  -H "Authorization: Bearer $NIO_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "display_name": "In-Market Auto Q3",
    "description": "Households showing auto purchase intent",
    "parent_element_id": "6f0c1e94-2b3a-4d51-9c7e-5a8b0d2f1e33",
    "buyable": true,
    "datasets": [12345],
    "data_rates": [
      {
        "type": "create_data_rate_system",
        "rate_type": "hybrid",
        "cpm_rate": 2.50,
        "percent_of_media_cost_rate": 0.15
      }
    ]
  }'
```

The connector checks the dataset's schema against The Trade Desk when you attach it, and rejects a dataset TTD would not accept.

## 8. Synchronize

Attaching a dataset records the link in Narrative. Synchronizing sends it to The Trade Desk and creates the delivery connection for you.

```bash theme={null}
curl -X POST https://thetradedesk.narrativeconnectors.com/taxonomies/synchronization-requests \
  -H "Authorization: Bearer $NIO_API_TOKEN"

# then poll
curl -s https://thetradedesk.narrativeconnectors.com/taxonomies/synchronization-requests \
  -H "Authorization: Bearer $NIO_API_TOKEN"
```

The request moves through `pending` and `in_progress` to `completed` or `failed`. Its `jobs.create_connections` counts report the delivery connections it created.

Each connection it creates uses the `third_party_v2` interface, a membership duration of 90 days, and delivers the data already in the dataset as well as new rows. To choose different values, see [Creating the connection yourself](#creating-the-connection-yourself).

A synchronization publishes every draft in your account, not only the element you changed, and only one can run at a time. See [Synchronize](/guides/connector-apis/the-trade-desk/managing-taxonomy-api#5-synchronize) for both rules.

<Note>
  The audience sells only once its element is `active` and TTD has approved it. See [Wait for approval](/guides/connector-apis/the-trade-desk/managing-taxonomy-api#6-wait-for-approval).
</Note>

## 9. Confirm delivery

Check that the connection exists:

```bash theme={null}
curl -s "https://api.narrative.io/v2/connections?dataset_id=12345" \
  -H "Authorization: Bearer $NIO_API_TOKEN"
```

Then watch the element's delivery statistics:

```bash theme={null}
curl -s https://thetradedesk.narrativeconnectors.com/taxonomies \
  -H "Authorization: Bearer $NIO_API_TOKEN"
```

Each element reports a `statistics` object, which is `null` while the element is still a draft:

| Field | Meaning |
| - | - |
| `received_ids_count` | Identifiers Narrative sent |
| `active_ids_count` | The subset The Trade Desk matched |
| `audience_size` | The audience size The Trade Desk reports |

The ratio of `active_ids_count` to `received_ids_count` is your match rate.

### Creating the connection yourself

The connection that synchronization creates always uses a 90-day membership duration and delivers existing data. To set `targeting_time_to_live_in_minutes` or `historical_data_enabled` yourself, leave the dataset off the element and create the connection once the element is `active`:

```bash theme={null}
curl -X POST https://api.narrative.io/v2/connections \
  -H "Authorization: Bearer $NIO_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "connections_dataset",
    "dataset_id": 12345,
    "profile_id": "f692fb18-9d47-4c2a-b1e6-3c5f8a0b7d21",
    "quick_settings": {
      "type": "third_party_v2",
      "third_party_data_type": "third_party",
      "provider_elements": [
        {
          "provider_element_id": "8c3d5b21-7e4f-4a90-b6c2-1f9a0e7d4b58",
          "display_name": "In-Market Auto Q3"
        }
      ],
      "targeting_time_to_live_in_minutes": 43200,
      "historical_data_enabled": false
    }
  }'
```

Both `type` fields are required. The outer one identifies what you are connecting, and the inner one selects the delivery interface. Always send them.

`targeting_time_to_live_in_minutes` is how long a delivered identifier stays active in TTD before expiring. The clock rides on every record, so each delivery renews it. See [Audience membership duration](/reference/connectors/the-trade-desk#audience-membership-duration) for the full behavior.

<Warning>
  `historical_data_enabled` decides whether data already in the dataset is delivered or only rows written after the connection is created. It cannot be changed once the connection exists.
</Warning>

See [Third-party v2 connection settings](/reference/connectors/the-trade-desk#third-party-v2) for every field.

## Keeping the audience current

Write new data to the dataset. The connection keeps running, so new rows reach The Trade Desk without further calls, and you do not touch the element or its price.

Refresh the dataset more often than the membership duration, or members lapse between deliveries. Your source dataset must refresh at least every 90 days.

To add another audience, repeat steps 1 through 8. Attach several datasets before you synchronize, so one synchronization publishes all of them.

## Troubleshooting

### Dataset and upload problems

| Symptom | Cause | Fix |
| - | - | - |
| `403` on `/company-info/whoami` | The key was not created under **Settings → API Keys** | Create one there. Application credentials do not work with this flow |
| `400 Missing required field .write_mode` or `.schema.file_config` | Both are required on dataset creation | Add them |
| `400 file not found` on ingest | You sent the path you requested rather than the one Narrative returned | Use the `path` from the upload response |
| Record count stays at zero | The file did not match the schema | Check that the format matches `file_config.type` and that field names match your schema exactly |
| `404` on `/datasets/{id}/interfaces` | The dataset is not activated yet | Activate it first |
| The Trade Desk listed as rejected | The schema has no recognized identifier in the expected shape | Read `details.errors`, then create a new dataset with a corrected schema |
| `400 No sample is available` on mapping | Mapping needs a data sample | `POST /datasets/{id}/request-sample`, wait for the job, retry |

### Delivery problems

| Symptom | Cause | Fix |
| - | - | - |
| Attaching the dataset is rejected | The dataset's schema does not meet TTD's requirements | Check step 5, then create a new dataset with a corrected schema |
| Synchronization is refused for a missing brand ID | The connector profile has no Brand ID | Add your Brand ID to the profile |
| Synchronization is rejected as already in progress | Only one runs at a time | Wait for the running one to finish |
| No connection appears after synchronizing | The dataset was not in the element's `datasets` list when you synchronized | Attach it with `PUT`, then synchronize again |
| A connection you create yourself is rejected | The element is not `active` yet | Wait for the element to go live, or attach the dataset to it instead |
| `statistics` is `null` | The element is still a draft | Synchronize it |

## Getting help

Contact your Narrative relationship manager with your company ID, the dataset ID, the element ID, and the failing request and response. For Trade Desk review status and Brand ID questions, your TTD account representative is the faster path.

***

## Related content

<CardGroup cols={2}>
  <Card title="Managing your taxonomy via the API" icon="sitemap" href="/guides/connector-apis/the-trade-desk/managing-taxonomy-api">
    Build, price, and maintain the elements audiences are sold from
  </Card>

  <Card title="First-party delivery via the API" icon="user" href="/guides/connector-apis/the-trade-desk/first-party-delivery-api">
    Deliver into one advertiser's own seat instead
  </Card>

  <Card title="The Trade Desk Connector" icon="bullhorn" href="/reference/connectors/the-trade-desk">
    Full settings reference, identifiers, and status vocabularies
  </Card>

  <Card title="API Keys" icon="key" href="/account-settings/api-keys">
    Create and rotate keys for programmatic access
  </Card>
</CardGroup>
