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

> Upload hashed identifiers to Narrative and deliver them into a new or existing Meta Custom 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 Custom Audience in one Meta ad account. You can create a new Custom Audience or add to one that already exists.

Before you start, complete the [prerequisites](/guides/connector-apis/meta#prerequisites) and [set up API access](/guides/connector-apis/meta#set-up-api-access). You need a profile ID and an eligible ad account ID, with its `act_` prefix.

## 1. Create the dataset

The dataset must carry at least one identifier the connector recognizes. Each identifier is a column named after the identifier, such as `sha256_hashed_email`, holding an object with a `value` property. See [Supported identifiers](/reference/connectors/meta#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": "spring_buyers_meta",
    "display_name": "Spring Buyers (Meta)",
    "description": "Audience for Meta",
    "write_mode": "append",
    "schema": {
      "file_config": { "type": "json", "normalize_field_names": false },
      "type": "object",
      "properties": {
        "sha256_hashed_email": {
          "type": "object",
          "properties": { "value": { "type": "string" } }
        }
      }
    }
  }'
```

Record the `id` in the response.

## 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"`. Activation locks the schema, so activate only once the shape is settled.

## 3. Upload and ingest your file

Each line of the file is one member. Trim and lowercase each email, then hash it with SHA-256. See [Hashing PII for Upload](/guides/ingestion/hashing-pii) for the formatting rules.

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

Request an upload URL, send the file to it, then ingest the file into the dataset:

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

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

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>"}'
```

The upload URL carries its own signature, so send no authorization header with the `PUT`. Pass the `path` from the first response as `source_file`.

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 minute or two.

## 4. Confirm Meta 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 Facebook Connector (`"app_id": 8`) in the `accepted` list:

```json theme={null}
{"app_id": 8, "interface_id": "audience_first_party_new"},
{"app_id": 8, "interface_id": "audience_first_party_existing"}
```

`audience_first_party_new` creates a new Custom Audience, and `audience_first_party_existing` adds to one you already have.

## 5. Deliver to a new Custom Audience

Create a connection. The connector creates the Custom Audience in Meta before it answers, so the audience exists in 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": "act_<AD_ACCOUNT_ID>",
      "audience_name": "Spring Buyers",
      "audience_description": "Bought in March or April",
      "membership_duration_days": 30,
      "historical_data_enabled": true
    }
  }'
# → 201 {"type": "connections_dataset", "id": "<CONNECTION_ID>",
#        "app": {"id": 8, "name": "Facebook Connector"}, "dataset_id": 12345,
#        "quick_settings": {...}, "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.

| Field | Meaning |
| - | - |
| `ad_account_id` | The ad account that gets the audience, with its `act_` prefix. Required for a system-user profile |
| `audience_name` | The audience's name in Ads Manager, up to 64 characters |
| `audience_description` | Optional, up to 2000 characters |
| `membership_duration_days` | Optional, 1 or more. Set once when the audience is created. Leave it out to use Meta's default. See [Audience membership duration](/reference/connectors/meta#audience-membership-duration) |
| `historical_data_enabled` | `true` delivers the rows already in the dataset as well as new ones. `false` delivers only rows written after the connection exists |

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

## 6. Confirm the connection

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

The response lists the connection with `"status": "active"`. In Ads Manager, under **Audiences**, the audience appears as a Customer List Custom Audience with the name you gave it, marked **Populating** while Meta matches the members. Its **Audience ID** column holds the ID you use in the next step.

## 7. Deliver to an existing Custom Audience

To send another dataset into an audience that already exists, whether Narrative created it or not, connect with the audience's Meta ID from the **Audience ID** column in Ads Manager:

```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": 12346,
    "profile_id": "<PROFILE_ID>",
    "quick_settings": {
      "type": "audience_first_party_existing",
      "audience_id": "<AUDIENCE_ID>",
      "historical_data_enabled": true
    }
  }'
# → 201 {"type": "connections_dataset", "id": "<CONNECTION_ID>",
#        "quick_settings": {"type": "audience_first_party_existing",
#                           "audience_id": "<AUDIENCE_ID>", "historical_data_enabled": true},
#        "status": "active", ...}
```

The audience must belong to an ad account the profile can reach. It keeps the membership duration it already has.

## Keeping the audience current

Write new data to the dataset. The connection keeps running, so new rows reach the Custom Audience without further calls.

## 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 archives it and stops further deliveries. The Custom Audience stays in Meta with its members. Delete it in Ads Manager if you no longer want it.

## Troubleshooting

| Symptom | Cause | Fix |
| - | - | - |
| `400` with `ad_account_id must be provided in quick settings` | The profile is a system-user profile and the request named no ad account | Add `ad_account_id` to `quick_settings` |
| `400` with `could not find audience with id ...` | The audience ID is wrong, or it belongs to an ad account the profile cannot reach | Copy the ID from the **Audience ID** column in Ads Manager for the same ad account |
| `400 Dataset Schema Incompatible with Connector` | No column is named after a supported identifier | Create a new dataset with a correctly named column, since activation locked the schema |
| The audience stays small in Ads Manager | Meta matched few members | Trim and lowercase each email before hashing it with SHA-256 |

## Getting help

Contact your Narrative relationship manager with your company ID, the dataset ID, and the failing request and response.

***

## Related content

<CardGroup cols={2}>
  <Card title="Sending conversion events via the API" icon="cart-shopping" href="/guides/connector-apis/meta/conversion-events-api">
    Send offline conversions to a Meta dataset
  </Card>

  <Card title="Meta Custom Audiences Connector" icon="meta" href="/reference/connectors/meta">
    Supported identifiers and audience membership duration
  </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.