Every write on this API requires an Idempotency-Key header. It is not optional and not only for retries. A write without one is refused:

Why it is mandatory

A network timeout does not tell you whether the server acted. Without an idempotency key your only options after a timeout are to retry and risk a duplicate order, or to not retry and risk losing the write. The key removes the choice: retrying is always safe, so you can always retry. Making it required rather than optional means the safe path is the default rather than something an integration remembers to opt into on the endpoints where it matters.

Using it

Generate a unique value per logical operation, and reuse that same value for every retry of that operation. A UUID is the obvious choice.
If that call times out, send it again byte for byte with the same $KEY. You get the original response back. The order is created once.
Do not reuse a key across different operations, and do not derive it from something that repeats, such as a customer ID or an order number alone. One key means one operation.

Exactly which outcomes spend a key

This is the part that is usually vague in an API and it is worth stating precisely, because it determines whether your SDK can keep using a key after a validation error.
A key is spent by what the request DID, not by what it asked for.
The second row is the one that matters in practice. If you POST with an invalid body and get 400, then fix the body and retry under the same key, it works. The key is not burned by the server’s own refusal, because nothing was created and so there is nothing to protect. An earlier version of this API did burn it, and the effect was that an SDK binding one key to one logical operation could never succeed with that key again after a single validation error. That is fixed.
Releasing the reservation on a 4xx looks like the simpler fix and is wrong. It would delete the row, so an identical retry would re-execute the handler instead of replaying the refusal.That matters when the refusal happened after a side effect. A 422 refund_not_retryable is returned after the payment provider was already called. Re-executing would call the provider a second time.So the refusal is still recorded and still replayed for the request that caused it. It simply no longer binds the key against a different request.

Concurrency

Two identical requests sent at the same instant under one key do not both execute. The key is reserved on insert, so the second one is serialised behind the first and answers 409 idempotency_key_in_progress. Retry it after a short pause and you will get the first one’s stored response.

What is compared

The key plus a fingerprint of the request. That is what lets a corrected body run on its own row after a refusal while still keeping every retry of that corrected request safe. You do not need to do anything for this to work. Send the same key with the same body and you replay; send the same key with a different body after a refusal and it executes once.

Deletes

DELETE counts as a write and takes a key like any other. Note that replay safety comes from the ledger rather than from the status code, so a replayed delete returns the original response rather than a second 404.

Retention

Keys are retained long enough to cover any realistic retry window, including a client that retries after a crash and restart. They are not retained forever, so do not treat an idempotency key as a permanent record of an operation. Store your own reference to the returned id if you need one.