Skip to main content
POST
Bulk create contacts
Create up to 1000 contacts in a single request. Emails are normalized and deduplicated. Requires the audience:write scope and is blocked for sandbox API keys. Send either emails (a flat list of addresses that all share the batch-wide list_ids, properties and topics) or contacts (one row per contact, each with its own properties, lists and topic subscriptions). Row-level values are applied on top of the batch-wide ones: a row’s properties key overrides the batch-wide value for that key, and a row-level opt_out beats a batch-level opt_in.
A 201 does not mean every row landed. Rows that fail validation are skipped and reported in errors — the rest of the batch still commits. Always check error_count / errors rather than treating a 2xx as full success.
already_existed and updated overlap by design. They answer different questions — “was the address already in the audience?” versus “did this request change the contact?” — so they do not sum to the number of submitted rows. A contact that already existed and got attached to a list is counted in both.
update_existing governs property merges and nothing else. By default (update_existing: false) existing contacts keep their properties; set it to true to merge instead — submitted keys overwrite, absent keys are preserved. Lists, topic opt-ins and topic opt_out entries are applied either way. A row-level opt_out drops an existing subscription whether or not the flag is set, so a consent or suppression import does not need it. The contacts array in the response returns { id, email, created } for every contact that exists after the request, in submission order. Feed those ids straight into bulk attach contacts to lists or bulk subscribe contacts to topics without a follow-up lookup.

Limits

Row error codes

Each entry in errors carries the zero-based index of the submitted row, the email, a human-readable error, and one of these error_code values:

Authorizations

Authorization
string
header
required

API key for authentication

Body

application/json

Bulk create contacts, up to 1000 per request. Send either emails (one shared property map for the whole batch) or contacts (a row per contact, each with its own properties, lists and topics) — not both. Emails are normalized (lowercased, trimmed) and deduplicated, with the last row for an address winning. Batch-wide list_ids and topics are unioned into every row.

emails
string<email>[]
required

Legacy shape: a flat list of addresses that all share properties and list_id.

Required array length: 1 - 1000 elements
Maximum string length: 255
Example:
list_id
string<uuid> | null

Optional list to add all contacts to. Kept for backwards compatibility; folded into list_ids.

properties
object | null

Custom property values applied to every contact in the batch. Works with both shapes; with contacts, a key set on a row overrides the batch value for that key.

contacts
object[]

One row per contact. A row that fails validation is skipped and reported in the response errors array — the rest of the batch is still written.

Required array length: 1 - 1000 elements
list_ids
string<uuid>[] | null

Lists every contact in the batch is added to.

Maximum array length: 50
topics
object[] | null

Topic subscriptions applied to every contact in the batch. A row-level opt_out wins over a batch-level opt_in for that contact.

Maximum array length: 50
update_existing
boolean
default:false

When true, contacts that already exist have their properties merged — submitted keys overwrite, others are preserved. When false their properties are left alone and they are counted in already_existed. This flag governs property merges only: lists, topic opt-ins and topic opt_out entries are always applied, so a row-level opt_out drops an existing subscription whether or not the flag is set.

Response

Contacts created

message
string
required
Example:

"Contacts created successfully."

data
object
required