Idempotency
A network error after you send a write leaves you unsure whether it happened. An Idempotency-Key makes retrying safe.
How it works
Send a unique Idempotency-Key header with any POST, PATCH or DELETE:
curl https://api.officehero.io/v1/invoices \
-H "Authorization: Bearer $OFFICEHERO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7d0d6f9e-2f4a-4b4e-9a53-3c1b2f6a9e10" \
-d '{"customer_id":"0199c3a1-7d4e-7b20-8a11-2f6c9d0e4a17","currency":"EUR","lines":[{"description":"Labour, 3 h","qty":3,"unit_price_minor":4500,"tax_rate":18}]}'
(Replace customer_id with one of your own Finance customers — take it from
an existing invoice, GET /v1/invoices. The id above is an example and
answers 422 invalid_reference — a refusal that is not stored, see below.)
For 24 hours, a second request from the same API key with the same key, method and path is not run again. You get the first response back — same status, same body — with one extra header:
Idempotency-Replayed: true
So the rule is simple: generate one key per logical operation (a UUID v4 is ideal), and reuse it for every retry of that operation.
What is stored
- Responses with a status from 200 to 499 are stored and replayed. A
422for a bad body (a missing or unknown member, a wrong type) replays as the same422. - Two kinds of
422are not stored: a reference to a record that does not exist (errors.common.validation.invalid_reference, e.g. an unknowncustomer_id) and a value the database cannot read as an id (errors.common.validation.invalid_input). A retry with the same key runs again, so you can correct the body and send it with the same key. - A 5xx is not stored: retry with the same key and the request runs again.
- Responses larger than 256 KiB are not replayed: the retry gets a
409(errors.platform.idempotency.replay_unavailable) telling you the first request already completed — read the resource instead. - Without the header, every request runs.
Mistakes it catches
Reusing a key with a different body is refused, because it is almost certainly a bug in the caller:
{
"type": "https://officehero.io/errors/idempotency-conflict",
"title": "Idempotency-Key reused with a different request body",
"status": 409,
"detail": "errors.idempotency.body_mismatch"
}
Bodies are compared as JSON, so the order of keys does not matter.
Scope
Keys are scoped to your company and your API key: two integrations — or
two keys of one integration — can never see each other’s stored responses,
even with the same Idempotency-Key.
Webhook deliveries also carry an Idempotency-Key header (equal to the event
id) so your receiver can deduplicate them; see Retries.