<Intro>

<EndpointHeader />

<Description>

Creates or updates up to 1,000 contacts in one request, matched by email address (trimmed and lowercased before matching). A new address creates a contact; an existing one is updated with the fields that entry supplies, and omitted fields keep their stored values. Optionally adds every contact in the request to up to 10 audiences.

Each entry succeeds or fails on its own: the response lists one result per contact in submission order (`created`, `updated`, or `failed` with the reason), and a failed entry does not abort the rest. If the request itself is invalid, for example when an entry in `audience_ids` does not exist, the whole request fails with a validation error and no contacts are written.

</Description>

</Intro>

<Payload kind="request">

<Field name="contacts" type="array of object" required>

<Description>

Contacts to create or update, matched by email address. Existing contacts are updated with the fields each entry supplies; omitted fields keep their stored values, so an entry can set fields but never clear them. New addresses create contacts.

</Description>

<FieldChildren kind="request">

<Field name="email" type="string" prefix="contacts." required>

<Description>

The contact's email address. Trimmed and lowercased before it is stored and checked for uniqueness. Unique within the workspace.

</Description>

</Field>

<Field name="first_name" type="string" prefix="contacts.">

<Description>

The contact's first name.

</Description>

</Field>

<Field name="last_name" type="string" prefix="contacts.">

<Description>

The contact's last name.

</Description>

</Field>

<Field name="external_id" type="string" prefix="contacts.">

<Description>

Your own identifier for this contact, such as a user ID in your system. Unique within the workspace when set.

</Description>

</Field>

<Field name="data" type="object" prefix="contacts.">

<Description>

Custom property values for this contact. Each key must be a property created via the contact properties API, and each value must be a string, number, or boolean matching the property's declared type (strings up to 500 characters); a null value is ignored. Unregistered or archived keys are rejected with a validation error. Total size is capped at 2 KB serialized.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="audience_ids" type="array of string">

<Description>

Audiences every contact in this request is added to. Contacts that are already members are left in place. Every listed audience must exist, or the whole request fails with a validation error and nothing is written.

</Description>

</Field>

<Field name="data_mode" type="string">

<Description>

How a supplied `data` object is applied to an existing contact. `merge` (the default) merges the supplied keys onto the contact's stored custom values, and a key with a `null` value deletes that one key. `replace` overwrites the whole stored `data` map with the supplied one. In both modes a contact that omits `data` keeps its stored values unchanged, so an import that touches one attribute never wipes the others.

</Description>

<Description>

Possible values: `merge`, `replace`

</Description>

</Field>

</Payload>

<Payload kind="response">

<Field name="data" type="array of object" required>

<Description>

One entry per contact in the request, in submission order.

</Description>

<FieldChildren kind="response">

<Field name="email" type="string" prefix="data." required>

<Description>

Email address this entry refers to, in the normalized (trimmed and lowercased) form it was matched and stored as.

</Description>

</Field>

<Field name="status" type="string" prefix="data." required>

<Description>

What happened to this contact. `created` means a new contact was created for the address; `updated` means an existing contact with the address was updated; `failed` means the entry was rejected and `error` explains why. A failed entry does not affect the other entries in the request.

</Description>

<Description>

Possible values: `created`, `updated`, `failed`

</Description>

</Field>

<Field name="contact_id" type="string" prefix="data.">

<Description>

ID of the created or updated contact. Absent when the entry failed.

</Description>

</Field>

<Field name="error" type="object" prefix="data.">

<Description>

Why this entry failed. Absent for successful entries.

</Description>

<FieldChildren kind="response">

<Field name="type" type="string" prefix="data.error." required>

<Description>

Machine-readable error category for this entry, such as `validation_error` or `conflict_error`, in the same vocabulary as the top-level error `type`. New categories may be added over time, so treat unrecognized values as a generic failure.

</Description>

</Field>

<Field name="message" type="string" prefix="data.error." required>

<Description>

Human-readable explanation of why this entry failed.

</Description>

</Field>

</FieldChildren>

</Field>

</FieldChildren>

</Field>

</Payload>