Idempotency and errors
Send idempotency, error format and status codes.
Idempotency
Pass idempotencyKey in the body on sends (required for email, minimum 8 characters):
the same key never produces two sends. There is no Idempotency-Key header.
Records: the natural key is enough
For contacts, lists and tags you do not need a key: an identical second call attaches
instead of creating or failing, so you never have to search first just to know whether you may
write. The response always says what happened via created, and the status separates the two
cases.
| Call | Natural key | On repeat |
|---|---|---|
POST /contacts | address per channel (identities) | 200 { created: false } on the existing contact, attributes merged |
POST /lists | name (case-insensitive) | 200 { created: false } with the id of the list already there |
POST /tags | name (case-insensitive) | 200 { created: false } with the id of the tag already there |
POST /lists/:id/contacts | list-contact pair | added: 0, no duplicates |
First time: 201. POST /contacts answers 409 ambiguous_identities in exactly one case: when
the identities of a single request belong to different contacts. Merging two contacts is
irreversible and we do not do it on our own initiative. The response carries the contactIds
involved, so the decision stays yours.
The attributes of an existing contact are merged with the current ones: send only what
changes, and null on a key deletes it. An identity's consent is touched only if you declare
it: omitting it never clears an opt-in collected elsewhere.
Error format
Error responses are JSON:
{ "error": "forbidden", "need": "email:send" }| Code | Meaning |
|---|---|
| 401 | Not authenticated (missing or invalid token) |
| 403 | Missing scope |
| 409 | Conflict (suppression, duplicate, address in use) |
| 422 | Invalid input |
| 402 | Plan limit reached |