Bulk create contacts
Create up to 1000 contacts in a single request. Send contacts to give each row its own properties, lists and topics, or the legacy emails array when the whole batch shares one property map. Emails are normalized and deduplicated; addresses that already exist are reported in already_existed, are always attached to the requested lists and topics and always have a row-level opt_out applied, and have their properties merged only when update_existing is set. Rows that fail validation are skipped and returned in errors — the request only fails with 422 when nothing could be written. The response carries the contact ids, so a follow-up call to the bulk list or topic endpoints needs no lookup. Requires the audience:write scope and is blocked for sandbox API keys.
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.
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 inerrors carries the zero-based index of the submitted row, the email, a human-readable error, and one of these error_code values:
Authorizations
API key for authentication
Body
- Option 1
- Option 2
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.
Legacy shape: a flat list of addresses that all share properties and list_id.
1 - 1000 elements255Optional list to add all contacts to. Kept for backwards compatibility; folded into list_ids.
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.
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.
1 - 1000 elementsLists every contact in the batch is added to.
50Topic subscriptions applied to every contact in the batch. A row-level opt_out wins over a batch-level opt_in for that contact.
50When 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.