Generated from Postwind commit a19b2e4 OpenAPI 0.1.0 synced

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 POST under /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, field Idempotency-Key, code invalid. 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.