Skip to content

Idempotency ​

A client that did not see the response (a network cut, a timeout, the automatic retry of an HTTP library) replays its request. Without memory, POST /api/v1/workflows would create two workflows and POST /api/v1/messages/send would send two mails. With an Idempotency-Key, the replay returns the original response, identical, and nothing happens twice.

Which routes accept it ​

Every POST that creates or triggers something: creating a workflow, duplicating it, publishing it, running it (test, run), retrying or cancelling executions, emitting a signal, sending or replying to a message, importing contacts or rows, installing a template, creating a table, a column or rows, deciding an approval, sending from the review, starting an analysis or an assistant conversation.

In the reference, these routes carry the note "This operation accepts an Idempotency-Key header"; in the OpenAPI document, the extension x-idempotency-key: true and a header parameter Idempotency-Key. A key sent to any other route is ignored.

How to use it ​

Generate a key per intention (a UUID is ideal), send it with the request, and send the same key again if you have to retry:

http
POST /api/v1/workflows HTTP/1.1
Authorization: Bearer mk_…
Content-Type: application/json
Idempotency-Key: 6f1d4c2e-6b3a-4a0e-9d2b-7c5e1f0a8b43

{ "name": "Invoice triage" }
  • The key is 1 to 200 characters, chosen by you. An empty or longer key is 400 idempotency.invalid_key.
  • The memory is per caller and per route: the same key on POST /api/v1/workflows and on POST /api/v1/tables are two different memories, and two API keys (or two members) never share one. The caller is the API key when there is one, the member otherwise.
  • The memory lasts 24 hours. After that, the same key starts a fresh request.

The three outcomes ​

SituationWhat happens
First timeThe key is reserved, the route runs, the status and body of its response are stored. The response is the normal one.
Replay, same key, same requestThe stored response is returned, same status and same body, without running the route again. The response carries the header Idempotency-Replayed: true.
Reuse, same key, different request422 idempotency.key_reused. Returning the response of another request would be worse than duplicating.

"Same request" is the URL and the JSON body, compared byte for byte after serialisation. Reordering keys in the body or changing a value makes it a different request.

Two more cases:

  • A replay that arrives while the first request is still running gets 409 idempotency.in_progress with Retry-After: 1. Wait and replay.
  • A response with a 5xx status releases the key: after a server failure you must be able to retry, and the retry runs the route for real.

What is not memorised ​

The stored response is capped at 64 KB. Above that, the request runs normally but is not memorised, and the response says so with Idempotency-Replayed: unsupported. A replay would then run the route again. In practice, every creating route answers well under this limit; the cap exists for safety.

A request without Idempotency-Key is never memorised and never deduplicated: replaying it does what it says, twice.

Routes that are idempotent by nature ​

Some routes do not need the header because calling them twice has the same effect as once: POST /api/v1/workflows/{id}/resume on a workflow that is not paused returns 200, POST /api/v1/api-keys/{id}/revoke keeps the first revocation date, POST /api/v1/executions/incidents/{id}/acknowledge keeps the first instant. PUT routes replace a state and can be replayed as well; PUT /api/v1/workflows/{id}/draft offers expectedDraft for concurrency control instead (see Workflows).