# Idempotent writes

A client that retries a write after a timeout can't tell whether the first
try worked. So every route but a `GET` honours the `Idempotency-Key`
header: the client sends the same key with each try, and a repeat gets the
first try's answer. The runtime handles the key entirely; the action never
sees it.

HTTP already calls `PUT` and `DELETE` idempotent, but a retried `DELETE`
answers 404 where the first answered 204, and the action behind any write
may raise events or call providers. So the key covers them too.

- Before the action runs, the runtime claims the key, scoped to the
  caller.
- Afterwards, it keeps the response with a digest (a hash) of the request,
  in the action's transaction, for 24 hours.
- A repeat gets the kept response.
- The same key with a different request fails `idempotency_key_reused`
  (422). A repeat while the first is still running fails `conflict` (409).
- Successes and failures the action returns are kept. `unavailable` and
  internal failures are not, so a retry runs again.
- Calls to outside services in the action send keys derived from the
  client's key ([Calling](/docs/calling.md)).

An API can require the key with `idempotency: Idempotency.required`, and
OpenAPI lists the header. The default is `Idempotency.optional`. When a
repeat counts as the same request is under
[How failures reach the client](/docs/how-failures-reach-the-client.md).

- **Whose keys they are.** A signed-in caller's keys are kept under their
  user id. A route anyone may call keeps its keys under `anonymous`.
- **A request that never finishes.** The claim holds no response and lasts
  only for the route's deadline and five seconds, so it lapses.
- **What a repeat answers with.** The kept status, body and code, under the
  repeat's own `x-request-id`.
- **Where keys are kept.** In the store the action writes. An action that
  writes no store but emits, or that makes a changing call, keeps them in
  the store `events: { store: }` names. One that does none of these keeps
  none, since running it again changes nothing.
