What Is an Idempotent Request? β Definition and How It Works
Learn which HTTP methods are idempotent, why it decides what can be retried, and how idempotency keys make POST safe.
An idempotent request produces the same server state whether it is sent once or many times. It is the property that decides whether a retry is safe.
1. Which Methods Are Idempotent
| Method | Idempotent | Safe |
|---|---|---|
| GET | Yes | Yes |
| HEAD | Yes | Yes |
| OPTIONS | Yes | Yes |
| PUT | Yes | No |
| DELETE | Yes | No |
| POST | No | No |
| PATCH | No | No |
Safe means no intended state change at all; idempotent means repeating causes no additional change. GET is both. PUT and DELETE change state but repeating changes nothing further. POST and PATCH are neither.
2. Why PUT Is and POST Is Not
PUT /items/42 with a full representation sets that item to that state. Sending it five times leaves the same result.
POST /items creates a new item. Sending it five times creates five items.
The distinction is that PUT names the target and POST asks the server to choose one. Idempotency follows from that, not from whether the method modifies anything.
3. DELETE Is Idempotent Despite the Status Code
DELETE /items/42 twice leaves item 42 deleted. The first returns 204, the second 404 β different responses, same resulting state.
Idempotency is a property of state, not of the response. A client that treats a 404 on a retried DELETE as a failure has misread the guarantee.
4. The Retry Rule
IDEMPOTENT = {"GET", "HEAD", "OPTIONS", "PUT", "DELETE"}
def may_retry(method, status, error):
if method.upper() not in IDEMPOTENT:
return False # unless an idempotency key is in use
if error in {"connect_timeout", "connection_reset", "dns_failure"}:
return True # request may never have arrived
return status in {408, 429, 500, 502, 503, 504}
The hardest case is a read timeout on a POST. The request was sent, and no response arrived, so you cannot know whether the server processed it. Retrying may duplicate; not retrying may lose. Neither is correct without more information β which is exactly the gap idempotency keys close.
5. Idempotency Keys Make POST Retryable
A client-generated unique key on a POST lets the server deduplicate:
POST /payments
Idempotency-Key: 8f1c0d9a-3e5b-4a71-9f2e-5c7d1b0a4e63
The server stores the key with the result. A repeat of the same key returns the original result instead of acting again.
Two rules make it work: generate the key before the first attempt and reuse it across all retries, and never reuse a key for a genuinely different request.
Where an API supports this, POST becomes as retryable as PUT. Where it does not, a read timeout on a POST needs a reconciliation query β ask the server whether the effect occurred β rather than a blind retry.
6. Idempotency Is Not About the Response
A GET whose response differs each time β a clock endpoint, a random result, a paginated feed β is still idempotent, because it changes no state.
Conversely a POST that happens to return the same body each time is not idempotent if it created something each time. The property concerns effects, and confusing the two leads to unsafe retries of endpoints that looked stable.
7. In Collection Work
Almost all collection is GET, which is why retries are usually straightforward. The exceptions deserve attention:
- POST-based search endpoints β technically non-idempotent but usually harmless, since they only read. Verify rather than assume.
- GraphQL over POST β a query is safe; a mutation is not. The method does not distinguish them, so your retry logic must inspect the operation.
- Session or token creation β non-idempotent, and retrying may invalidate the session the first attempt created.
- Job submission APIs β the case where an idempotency key or a status lookup genuinely matters.
8. Summary
An idempotent request leaves the same server state however many times it is sent: GET, HEAD, OPTIONS, PUT, and DELETE qualify; POST and PATCH do not.
Retry idempotent requests freely on timeouts and 5xx; for POST, generate an idempotency key before the first attempt and reuse it across retries, or reconcile by querying the server β and remember that a GraphQL POST hides both a safe query and an unsafe mutation behind the same method.