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.

CallNatural keyOn repeat
POST /contactsaddress per channel (identities)200 { created: false } on the existing contact, attributes merged
POST /listsname (case-insensitive)200 { created: false } with the id of the list already there
POST /tagsname (case-insensitive)200 { created: false } with the id of the tag already there
POST /lists/:id/contactslist-contact pairadded: 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" }
CodeMeaning
401Not authenticated (missing or invalid token)
403Missing scope
409Conflict (suppression, duplicate, address in use)
422Invalid input
402Plan limit reached