> ## 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 Audiences to Pinterest via the API

> Upload hashed emails to Narrative and deliver them into a new or existing Pinterest audience over the API

This guide shows you how to build an unattended integration that uploads an audience to Narrative and delivers it into a Pinterest audience. Pinterest stores the members in a customer list, and the audience targets that list. You can have Narrative create a new customer list and audience in one of your ad accounts, or deliver into an audience you already have.

Before you start, complete the [prerequisites](/guides/connector-apis/pinterest#prerequisites) and [set up API access](/guides/connector-apis/pinterest#set-up-api-access). You need a profile ID and an ad account ID from [List your ad accounts](/guides/connector-apis/pinterest#list-your-ad-accounts).

Requests to `api.narrative.io` use your API key. Requests to `pinterest.narrativeconnectors.com` use the installation token.

## 1. Create the dataset

The dataset must carry at least one identifier column that Pinterest can match on. [Required columns](#required-columns) lists them.

```bash theme={null}
curl -X POST https://api.narrative.io/datasets \
  -H "Authorization: Bearer $NIO_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "spring_buyers_pinterest",
    "display_name": "Spring Buyers (Pinterest)",
    "description": "Hashed emails for Pinterest",
    "write_mode": "append",
    "schema": {
      "file_config": { "type": "json", "normalize_field_names": false },
      "type": "object",
      "properties": {
        "sha256_hashed_email": {
          "type": "object",
          "properties": { "value": { "type": "string" } }
        }
      }
    }
  }'
# → {"id": 12345, "company_id": 1234, "write_mode": "append", ...}
```

Record the `id` in the response.

### Required columns

Both audience interfaces take the same schema. The dataset needs at least one of these identifier columns:

| Column | Shape of each value |
| - | - |
| `sha256_hashed_email` | `{"value": ...}` |
| `android_advertising_id` | `{"value": ...}` |
| `mobile_id_unique_identifier` | `{"value": ..., "type": "..."}` |
| `unique_id` | `{"value": ..., "type": "..."}` |
| `narrative_id` | `{"context": "...", "value": ...}` |

Each identifier column is named after its Rosetta Stone attribute, and the connector matches on that column name. A column holds either a plain string or an object. In the dataset schema, declare a string column with `"type": "string"`, and declare an object column with `"type": "object"` and its properties:

```json theme={null}
{
  "type": "object",
  "properties": {
    "<string_attribute>": { "type": "string" },
    "<object_attribute>": {
      "type": "object",
      "properties": { "<property>": { "type": "<type>" } }
    }
  }
}
```

Send an object column as an object in each row:

```json theme={null}
{"<string_attribute>": "...", "<object_attribute>": {"<property>": "..."}}
```

See your connector's reference page for the attributes it accepts and their exact shapes.

A row with one identifier looks like this:

```json theme={null}
{"sha256_hashed_email": {"value": "<SHA-256 of the trimmed, lowercased email>"}}
```

See [Supported identifiers](/reference/connectors/pinterest#supported-identifiers) for how the destination matches each identifier.

## 2. Activate the dataset

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

The response is `201` with the dataset, now `"status": "active"`.

## 3. Upload and ingest your file

Each line of the file is one row that matches the schema you declared in [step 1](#1-create-the-dataset). For the columns Pinterest accepts and their shapes, see [Supported identifiers](/reference/connectors/pinterest#supported-identifiers) and [Required columns](#required-columns).

You can load a file into a dataset over the API in two ways:

* **Signed-URL upload.** Your integration uploads one file of up to 3 GB, then asks Narrative to ingest it.
* **Managed S3 bucket.** You write files to an S3 bucket that Narrative manages, and Narrative ingests each batch on its own. Use a managed bucket for files larger than 3 GB, or for files that another system delivers on a schedule.

### Choose a file format

The dataset's `file_config.type` sets the format of every file you load into it. Parquet and JSON Lines both hold object columns, which nest properties inside one column:

* **Parquet** (`parquet`) is the most compatible format for connector datasets. It stores nested struct columns and their types natively, and Narrative matches columns to the schema by name at every level.
* **JSON Lines** (`json`) holds one JSON object per line. Narrative matches each nested object to the schema by name.

CSV datasets (`flat`) hold only scalar columns, so they can't carry object columns.

### Upload a file with a signed URL

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

```bash theme={null}
curl -X POST https://api.narrative.io/uploads/my-file.jsonl \
  -H "Authorization: Bearer $NIO_API_TOKEN"
# → {"path": "<UPLOAD_PATH>", "url": "https://...", "expiry": "..."}

curl -X PUT "<url from the response>" --upload-file ./my-file.jsonl
```

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

<Warning>
  Keep the `path` from the response. Narrative assigns its own storage path, which does not match the name you requested, and the ingest request needs Narrative's path rather than yours.
</Warning>

Then ingest the file into the dataset, passing that `path` as `source_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": "<UPLOAD_PATH>"}'
```

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. Each ingested file adds a new snapshot to the dataset, and every active connection on the dataset delivers that snapshot.

### Write files to a managed S3 bucket

A managed bucket is an S3 bucket that Narrative creates for your company. You write each batch of files into its own folder under the dataset's path in the bucket, then write an empty `_NIO_COMMIT` file into that batch folder. Narrative ingests every file in the batch folder when the commit file appears, so you make no upload or ingest request. A file can be as large as S3 accepts.

See [Ingesting Files from a Managed S3 Bucket](/guides/ingestion/ingesting-files-from-a-managed-s3-bucket) to create the bucket, grant your AWS account access, and lay out the folders.

## 4. Confirm Pinterest accepts the dataset

Before you create a connection, ask Narrative which connector interfaces the dataset satisfies:

```bash theme={null}
curl -s https://api.narrative.io/datasets/{dataset_id}/interfaces \
  -H "Authorization: Bearer $NIO_API_TOKEN"
# → {"dataset_id": 12345,
#    "accepted": [{"app_id": <APP_ID>, "interface_id": "<INTERFACE_ID>"}, ...],
#    "errors": [{"app_id": <APP_ID>, "interface_id": "<INTERFACE_ID>", "details": {...}}, ...]}
```

The response checks the dataset's schema against every interface of the connectors your company has installed, and sorts the results into two lists:

* `accepted` lists each interface you can connect the dataset to, by the connector's `app_id` and the `interface_id`.
* `errors` lists each interface the schema does not satisfy. Its `details` hold the reason, such as `"required property '<column>' not found"`.

Add `?tags=<tag>` to check only the interfaces that carry that tag.

When the interface you want is under `errors`, the dataset's schema doesn't meet what the interface needs, for example a missing column or property. Activation locks the schema, so create a new dataset that fixes what the error names.

For Pinterest, add `?tags=pinterest` and look for the Pinterest Connector (`"app_id": 20`) in `accepted`. `audience_first_party_new` delivers to a new audience and `audience_first_party_existing` delivers to one you already have. Step 5 then checks the delivery settings with Pinterest itself.

```bash theme={null}
curl -s "https://api.narrative.io/datasets/{dataset_id}/interfaces?tags=pinterest" \
  -H "Authorization: Bearer $NIO_API_TOKEN"
# → {"dataset_id": 12345,
#    "accepted": [{"app_id": 20, "interface_id": "audience_first_party_existing"},
#                 {"app_id": 20, "interface_id": "audience_first_party_new"}, ...],
#    "errors": [...]}
```

## 5. Validate the settings

The connector can check the connection settings against Pinterest before you create anything. It confirms that the profile reaches the ad account:

```bash theme={null}
curl -s -X POST https://pinterest.narrativeconnectors.com/profiles/{profile_id}/validate-quick-settings \
  -H "Authorization: Bearer $INSTALLATION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "audience_first_party_new",
    "ad_account_id": "<AD_ACCOUNT_ID>",
    "audience_name": "Spring Buyers",
    "historical_data_enabled": true,
    "write_mode": "append"
  }'
# → {"valid": true, "validation_errors": []}
```

When a setting doesn't check out, `valid` is `false` and `validation_errors` says which one, for example `"Ad account <AD_ACCOUNT_ID> is not accessible. Please verify the ad account ID."`.

## 6. Create the connection

### Deliver to a new audience

To deliver to a new audience, create a connection with the `audience_first_party_new` interface. The connector creates a customer list and an audience in the ad account before it answers, both named `audience_name`, so they exist in Pinterest Ads Manager once you get `201`.

```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": "<PROFILE_ID>",
    "quick_settings": {
      "type": "audience_first_party_new",
      "ad_account_id": "<AD_ACCOUNT_ID>",
      "audience_name": "Spring Buyers",
      "audience_description": "Spring campaign buyers",
      "historical_data_enabled": true,
      "write_mode": "append"
    }
  }'
# → 201 {"type": "connections_dataset", "id": "<CONNECTION_ID>",
#        "app": {"id": 20, "name": "Pinterest Connector"}, "dataset_id": 12345,
#        "installation_id": <INSTALLATION_ID>,
#        "quick_settings": {"type": "audience_first_party_new", "write_mode": "append",
#                           "ad_account_id": "<AD_ACCOUNT_ID>", "audience_name": "Spring Buyers",
#                           "audience_description": "Spring campaign buyers",
#                           "historical_data_enabled": true},
#        "profile_id": "<PROFILE_ID>", "profile": {"id": "<PROFILE_ID>", "name": "Example Brand"},
#        "status": "active"}
```

Both `type` fields are required. The outer one identifies what you are connecting, and the one inside `quick_settings` selects the delivery interface.

[What the connection does](/guides/connector-apis/pinterest#what-the-connection-does) describes every field, its allowed values, and its default.

Record the connection `id`. You need it to check the delivery and to stop it.

### Deliver to an existing audience

To add a dataset to an audience you already have in Pinterest, pick the audience from the ad account's audiences. Pinterest delivers data into customer list audiences, so filter the list with `audience_type=CUSTOMER_LIST`:

```bash theme={null}
curl -s "https://pinterest.narrativeconnectors.com/profiles/{profile_id}/ad-accounts/{ad_account_id}/audiences?page_size=1000&audience_type=CUSTOMER_LIST" \
  -H "Authorization: Bearer $INSTALLATION_TOKEN"
# → {"audiences": [{"id": "<AUDIENCE_ID>", "ad_account_id": "<AD_ACCOUNT_ID>",
#                   "name": "Loyalty Members", "audience_type": "CUSTOMER_LIST",
#                   "customer_list_id": "<CUSTOMER_LIST_ID>", "status": "READY", "size": 3754,
#                   ...}],
#    "bookmark": null}
```

The audiences list pages with `page_size` and `bookmark` the same way as [the ad accounts list](/guides/connector-apis/pinterest#list-your-ad-accounts). Record the `id` of the audience you want.

Validate the settings as in [step 5](#5-validate-the-settings), with the `audience_first_party_existing` interface:

```bash theme={null}
curl -s -X POST https://pinterest.narrativeconnectors.com/profiles/{profile_id}/validate-quick-settings \
  -H "Authorization: Bearer $INSTALLATION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "audience_first_party_existing",
    "ad_account_id": "<AD_ACCOUNT_ID>",
    "audience_id": "<AUDIENCE_ID>",
    "historical_data_enabled": true,
    "write_mode": "append"
  }'
# → {"valid": true, "validation_errors": []}
```

Then create the connection:

```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": "<PROFILE_ID>",
    "quick_settings": {
      "type": "audience_first_party_existing",
      "ad_account_id": "<AD_ACCOUNT_ID>",
      "audience_id": "<AUDIENCE_ID>",
      "historical_data_enabled": true,
      "write_mode": "append"
    }
  }'
# → 201 {"type": "connections_dataset", "id": "<CONNECTION_ID>",
#        "app": {"id": 20, "name": "Pinterest Connector"}, "dataset_id": 12345,
#        "quick_settings": {"type": "audience_first_party_existing", "write_mode": "append",
#                           "audience_id": "<AUDIENCE_ID>", "ad_account_id": "<AD_ACCOUNT_ID>",
#                           "historical_data_enabled": true},
#        "profile_id": "<PROFILE_ID>", "status": "active", ...}
```

[What the connection does](/guides/connector-apis/pinterest#what-the-connection-does) describes every field, its allowed values, and its default.

Several datasets can feed one audience. Create an `audience_first_party_existing` connection from each dataset to the same `audience_id`.

## 7. Check the upload counts

The connector reads the audience and customer list behind a connection from Pinterest:

```bash theme={null}
curl -s https://pinterest.narrativeconnectors.com/profiles/{profile_id}/connections/{connection_id}/audience-status \
  -H "Authorization: Bearer $INSTALLATION_TOKEN"
# → {"created_at": "2026-10-03T15:38:33.749179Z",
#    "audience": {"id": "<AUDIENCE_ID>", "name": "Spring Buyers", "status": "INITIALIZING",
#                 "size": 0, ...},
#    "customer_list": {"id": "<CUSTOMER_LIST_ID>", "name": "Spring Buyers", "status": "processing",
#                      "num_uploaded_user_records": 25, "num_removed_user_records": 0,
#                      "num_batches": 1, ...}}
```

`customer_list.num_uploaded_user_records` counts the records Pinterest has received. `num_batches` counts upload batches, and most deliveries add one. `audience.status` and `audience.size` come from Pinterest's own matching, which Pinterest runs after it receives the records.

To see every Pinterest connection on a dataset or a profile:

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

curl -s "https://api.narrative.io/v2/connections?profile_id={profile_id}" \
  -H "Authorization: Bearer $NIO_API_TOKEN"
# → {"records": [{"type": "connections_dataset", "id": "<CONNECTION_ID>",
#    "app": {"id": 20, "name": "Pinterest Connector"}, "dataset_id": 12345,
#    "quick_settings": {...}, "status": "active", ...}]}
```

To hear when each delivery finishes, subscribe to [delivery notifications](/guides/connector-apis/pinterest#delivery-notifications). The connector sends `audience.delivery.completed` when a delivery to the audience finishes.

## Keeping the audience current

Write new data to the dataset with the same three calls as in [step 3](#3-upload-and-ingest-your-file). The connection keeps running, so new rows reach the audience without further calls. Each delivery raises the counts in [`audience-status`](#7-check-the-upload-counts):

```text theme={null}
num_uploaded_user_records  35 → 40
num_batches                 2 → 3
```

## Stopping delivery

```bash theme={null}
curl -X DELETE https://api.narrative.io/v2/connections/{connection_id} \
  -H "Authorization: Bearer $NIO_API_TOKEN"
# → 200 {}
```

Deleting the connection stops further deliveries. The audience and its customer list stay in Pinterest with their members. Delete them in Pinterest Ads Manager if you no longer want them. To remove the dataset as well, send `DELETE /datasets/{dataset_id}`.

## Getting help

Contact your Narrative relationship manager with your company ID, the dataset ID, the connection ID, and the `audience-status` response.

***

## Related content

<CardGroup cols={2}>
  <Card title="Inviting a partner via the API" icon="link" href="/guides/connector-apis/pinterest/connect-account-api">
    Let a partner connect their Pinterest account so you can deliver to it
  </Card>

  <Card title="Pinterest Connector" icon="pinterest" href="/reference/connectors/pinterest">
    Supported identifiers, profiles, and invites
  </Card>

  <Card title="Connector Interfaces" icon="plug" href="/concepts/data-activation/connector-interfaces">
    Why a dataset connects to an interface rather than a connector
  </Card>

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


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.