pickuma.
Dev Knowledge

Idempotent, Explained: Why It Matters for APIs and Retries

What idempotent means in practice — how it shapes HTTP method semantics, why it makes retries safe over flaky networks, and how the idempotency key pattern stops duplicate charges.

5 min read

A request times out. You don’t know whether the server got it. Do you retry? Whether that’s safe comes down to one word: idempotent.

What idempotent actually means

An operation is idempotent if performing it many times produces the same result as performing it once. The state of the system after one call is identical to the state after ten identical calls.

The classic example is setting a value. x = 5 is idempotent — run it once or a hundred times and x is 5 either way. Compare that to x = x + 1, which lands somewhere different every time. The first is idempotent; the second is not.

Note what idempotent does not mean. It does not mean “no effect.” DELETE /users/42 changes the world the first time — the user is gone. But running it again leaves the system in the same state: the user is still gone, and the outcome is the same. The effect doesn’t repeat. That’s the distinction that matters.

This is also different from safe, a related term that gets confused with it. A safe operation has no side effects at all — it only reads. A GET request is safe: it shouldn’t change anything. Every safe operation is idempotent (reading twice changes nothing), but not every idempotent operation is safe. DELETE is idempotent but definitely not safe.

How HTTP methods encode this

HTTP method semantics are built on these two properties, and the spec defines which methods are which.

  • GET — safe and idempotent. It reads a resource. Repeating it returns the same thing and changes nothing.
  • PUT — idempotent, not safe. It replaces a resource with the body you send. PUT /users/42 with the same payload always leaves user 42 in the same final state, no matter how many times you send it.
  • DELETE — idempotent, not safe. Deleting an already-deleted resource leaves it deleted.
  • POST — neither safe nor idempotent (in the general case). POST /orders typically creates a new resource each time. Send it twice and you get two orders.

That last line is the whole problem with retries. GET, PUT, and DELETE can be retried freely. POST cannot — a blind retry risks a duplicate.

The idempotency key pattern

Some writes genuinely create something new and can’t be made idempotent by their nature — charging a card, placing an order, sending a message. You still want to retry them safely, because networks fail in the worst way: the server processes your request, then the response gets lost on the way back. The client thinks it failed; the work is actually done.

The fix is an idempotency key. The client generates a unique identifier (often a UUID) for the logical operation and sends it with the request, usually in a header:

POST /charges
Idempotency-Key: 9f8c2a1e-7b3d-4e6a-9c12-0f5a8b2d4e10
{ "amount": 4200, "currency": "usd" }

The server records that key the first time it processes the request, along with the result. If a request arrives with a key it has already seen, the server skips the work and returns the stored result instead of doing it again. The client can now retry the same request as many times as it wants — same key, same outcome, exactly one charge.

This is how payment APIs handle retries; Stripe’s API, for instance, uses an Idempotency-Key header for exactly this reason. The pattern effectively makes a non-idempotent POST behave idempotently, scoped to that one key. A few details worth getting right: generate the key once per logical operation and reuse it across retries (a fresh key per attempt defeats the purpose), and have the server expire stored keys after some window so the table doesn’t grow forever.

FAQ

FAQ

Is POST ever idempotent?+
By default no — a plain POST usually creates a new resource on each call. But you can make a specific POST endpoint behave idempotently by accepting an idempotency key and deduplicating repeated keys server-side. The HTTP method is still POST; the endpoint's behavior is what changes.
What's the difference between safe and idempotent?+
Safe means no side effects at all — the operation only reads, like GET. Idempotent means repeating the operation has the same effect as doing it once, even if that effect changes state, like DELETE. Every safe method is idempotent, but idempotent methods such as PUT and DELETE are not safe.
Why not just retry every failed request?+
Because a timeout doesn't tell you whether the server actually processed the request before the response was lost. Retrying a non-idempotent operation in that situation can create duplicates — two orders, two charges. Retries are only safe when the operation is idempotent or protected by an idempotency key.

Related reading

See all Dev Knowledge articles →

Get the best tools, weekly

One email every Friday. No spam, unsubscribe anytime.