Idempotency
Retry sends safely — the same key never sends twice
Network timeouts happen. To retry POST /v1/messages without risking a
duplicate send, pass an Idempotency-Key header — any unique string that
identifies the operation (an order id, a job id):
POST /v1/messages/batch accepts the same header; there the stored response is
the whole positional batch result.
Semantics
- The first request with a key performs the send. The key is claimed in the same database transaction as the message, so a request that fails before committing leaves no claim behind and the key stays usable.
- A retry with the same key and the same body replays the stored
response — the same message
id, no second send. - A concurrent retry while the original is still in flight serializes behind it: it waits for the original to commit, then replays that response.
- The same key with a different body returns
409 idempotency.body_mismatch— a key identifies one exact operation. - A claim that exists but cannot be replayed returns
409 idempotency.conflict. Retry shortly; if it persists, quote therequest_id.
Claims are scoped to the workspace and retained for 24 hours, after which the key is free to use again.
Replay in practice
Practical guidance
- Generate the key where the business operation is born (e.g.
invoice-{id}-receipt), not per HTTP attempt — that’s the whole point. - Safe to adopt gradually: requests without the header behave exactly as before (each attempt sends).
- Replay matching is semantic, not textual: the key is matched against a
hash of the parsed request, so whitespace and field order don’t matter, and
an omitted optional field is the same as an explicit
null. Changing any actual value — a recipient, the subject, the body — is a different operation and tripsidempotency.body_mismatch.