Idempotency
A POST that gets no answer may or may not have run. Send it with an Idempotency-Key header, and a repeat with the same key runs it at most once.
The header
- It works on every
POSTunder/api/v1, including those without a body, such as launching a campaign or verifying a domain. On other methods the header has no effect. - The value is 1 to 128 visible ASCII characters (
!to~), without spaces. A UUID will do. - An empty value, a value longer than 128 characters, a value with a space or outside ASCII, or the header sent twice:
422 validation_failed, fieldIdempotency-Key, codeinvalid. The body is not read and nothing runs. - Without the header the request runs every time it arrives.
How a key is matched
- The key belongs to the API key that sent it. Two API keys with the same
Idempotency-Key, even two keys of one project, make two independent requests. Neither gets the stored answer of the other. - One key is bound to one request: the method, the path with its query, and the body byte for byte. A body serialized another way (spaces, order of fields) is another body.
- The window is 24 h from the first request. After it the key can be used again: the same request runs again and creates a new resource.
What a repeat gets
| The repeat | The answer |
|---|---|
Same key, same method, path and body, after a 2xx |
The stored answer: status, Content-Type, Location, Cache-Control and body, plus Idempotent-Replayed: true. Nothing runs again |
| Same key, another body, path or method | 422 idempotency_key_reused, without errors. Nothing runs |
| Same key and body, while the first request has not answered (here, on another node, or at the same moment) | 409 idempotency_key_in_progress. Nothing runs; try again later |
Same key and body, after the first request answered 504 storage_timeout |
409 idempotency_key_in_progress until the key’s 24 h end |
Same key, after any other answer (4xx, 500) |
The request runs again |
Only a 2xx answer is stored. After a 4xx nothing was written, so a fixed request or a repeat with the same key is safe. After a 500 the resource may exist: a repeat runs the operation again.
A 504 is not stored and does not release the key, because its outcome is unknown. The operation never runs a second time under that key. Check whether the resource exists, then go on with a new key.
A request runs to its end even when the client disconnects. Its answer is stored, and a repeat gets it.
Creating a key
The 201 that creates an API key carries the key string. The key string is never stored, so the replay of that answer has the same status, Location, Cache-Control: no-store and body, but "key": null. A client that lost the first answer revokes the key by the id in the replay and creates a new one with a new Idempotency-Key.
Order
Requests that fail with 401 (the key) or 429 (the rate limit) never reach idempotency and do not take the key. Nor does an unknown path (404). The header is checked after the rate limit and before the body.
Example
IDEM=$(uuidgen)
curl -X POST "$POSTWIND_URL/api/v1/projects/$PROJECT_ID/lists" \
-H "Authorization: Bearer $POSTWIND_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $IDEM" \
-d '{"name": "Product updates"}'
# The same call again: no second list, the first answer comes back
# with the header Idempotent-Replayed: true.
curl -i -X POST "$POSTWIND_URL/api/v1/projects/$PROJECT_ID/lists" \
-H "Authorization: Bearer $POSTWIND_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $IDEM" \
-d '{"name": "Product updates"}'
MCP
MCP tools have no Idempotency-Key. A tool call that got no answer is checked with the matching list tool before it is called again. See Tool rules.