Basis Lab

Answers · The network and the web

Why do I need an idempotency key if my operation is already idempotent?

In shortYou need the key because repeating an operation safely still leaves the caller unsure what happened, and the key returns the first answer.

You need the key because idempotence describes the effect of repeating an operation, not the answer the caller receives when they repeat the request. An operation that is safe to apply twice can still leave the caller unable to tell whether the first application happened, and the key is what makes their retry return the first answer rather than a second outcome.

The operation and the answer are different things

Consider deleting an order. Delete it once and it is gone. Delete it twice and it is still gone, so the stored state is idempotent. But the first call may return a success response, while a retry processed later returns not found, because the order no longer exists. The caller who timed out cannot distinguish “my delete worked” from “the order was never there”, even though the stored state is the same either way. The work was idempotent. The report of the work was not.

\[ S_2 = S_1 \quad \text{does not imply} \quad R_2 = R_1. \] Idempotence gives the left equality for the stored state after a repeat. A keyed retry gives the right equality by replaying the stored response.

What the key adds

The key is a name for one intent, generated by the caller before the first attempt and reused on every retry of that intent. The service records it before doing the work and marks it in progress. When the work completes, it stores the status code and the whole response body against the key. A later request with the same key and the same request digest replays that stored response verbatim. A later request with the same key but a different digest is refused, because the caller has reused a key for something else. If the first call is still in progress, the honest answer is a conflict asking them to wait, not a second execution.

The natural guess is that an idempotent operation makes the key redundant: just run it again and the state will be right. That protects the state only after the first attempt has completed, and only if the repeat is serialised behind it. Two identical retries can arrive while the first is still running, both read the same pre-state, and both do the work. Recording the key before the work turns the second into a conflict or a replay. And even when the first attempt has completed, running the operation again can produce a different answer, as the delete example shows, so the caller is left with two different reports of one intent.

The value cannot be derived from the request body. Two charges of the same amount to the same card for the same order can be a retry of one payment or a genuine second purchase. The contents are identical and the intent is not. Hashing the body and treating matching hashes as duplicates refuses the genuine second purchase, which is a silent failure: the customer is told no. Only the caller knows whether the new request is the same intent, so only the caller can generate the key.

Where it stops helping

The guarantee has a window. If the record is kept for sixty seconds and a retry arrives after ninety, the key is unknown and the request is treated as new. A window of twenty-four hours covers automatic retries, queue backlogs drained within the hour, and overnight jobs rerun the next morning. The storage cost is small. At one million keyed requests a day, two kilobytes per record and one day of retention, the records occupy 1,000,000 × 2 KB × 1 = 2,000,000 KB, which is about two gigabytes. Keeping them a week gives 2 × 7 = 14 gigabytes. The cost of a short window is not storage; it is the overnight rerun that arrives after the key has expired and doubles a real payment.

Two details decide whether the window is a contract or a trap. The key record and the work must commit together, or a crash between them leaves the key in progress forever and every retry gets a conflict. And the retention period must be published, because a caller cannot plan retries around a window nobody told them about.

Worth keeping

  • An idempotent operation guarantees the same effect when repeated, not the same response.
  • The key has to come from the caller because only the caller knows whether a second request is a retry or a new intent.
  • Storing the whole first response against the key is what makes a retry correct rather than merely harmless.

Where this goes next

This question is one step of Designing an Interface to Call, a course that works through its subject from the beginning. The page that teaches this part of it is When the Caller Calls Twice, and it reads the whole way through whether or not you have an account.

What an account changes is the practice: problems standing under every idea, marked with a reason rather than a tick, in a copy of the course that is yours.

Read that pageThe whole course

Other questions people ask about this

  1. What does it mean for an API method to be safe?A safe method is one that changes nothing on the server when it is called, so a caller may repeat it freely and a cache or a crawler may use it without anything being altered.
  2. What's wrong with naming an API endpoint after an action like /getUser?Naming an endpoint after an action bakes one operation into its address, so every new operation needs a new endpoint and the thing being acted on never gets a stable name.
  3. How do I design an error response that a client can actually handle?Give every failure a fixed code a program can branch on and say plainly whether trying again could help, then put the human explanation last.
  4. What's the difference between safe and idempotent in HTTP methods?Safe means the call changes nothing; idempotent means repeating the call leaves the same result, even if the first call changed something.
  5. Where should a session ID be stored so JavaScript can't read it?Store it in an HttpOnly cookie set by the server; that is the only browser storage JavaScript running on the page cannot read.
  6. Does signing out actually invalidate my token on the server?No. Signing out clears your browser's copy of the token, but the server keeps accepting that token until it expires.

Read alongside