Answers · The network and the web
What's the difference between safe and idempotent in HTTP methods?
In shortSafe means the call changes nothing; idempotent means repeating the call leaves the same result, even if the first call changed something.
The two questions
Safe and idempotent answer different questions. A safe method changes nothing observable; an idempotent method may change things, but a second identical call leaves the state the same as after the first. Every safe method is idempotent, but many idempotent methods are not safe.
Say the state before a call is , after one call is , and after two identical calls is . Safe means . Repeatable, which is what HTTP calls idempotent, means . The second condition is weaker: it allows to differ from , provided the second call does not move the state again.
Why the guess fails
The natural guess is that idempotent means harmless, or that safe and idempotent are two words for the same property. Neither holds. A delete changes the world: the first call removes the thing. A second identical delete finds nothing to remove, but the state afterwards is the same as after the first call. That is idempotent with a real side effect.
The other half of the mistake is thinking the response has to be the same. It does not. The first delete may return 204 and the second 404. Idempotency is about the state left behind, not the status line. Confusing the two makes people reject the correct classification of delete, and then they design retry logic around the wrong property.
| operation | state after one | state after two | safe | repeatable |
|---|---|---|---|---|
| read order 7 | unchanged | unchanged | yes | yes |
| replace order 7 with {x} | {x} | {x} | no | yes |
| delete 7 from {7,8} | {8} | {8} | no | yes |
| add 50 to balance 100 | 150 | 200 | no | no |
| create order, server picks id | one order | two orders | no | no |
| create order with id 7 | order 7 | order 7 | no | yes |
Take a balance of 100 and an operation that adds 50. One call gives 150. A second identical call gives 200, not 150, so the operation is not repeatable. Now wrap the same movement in a transfer record whose identifier the caller supplies, say transfer 7. The first call records transfer 7 and moves the balance from 100 to 150. The second call arrives with transfer 7, finds it already recorded, returns the existing record, and leaves the balance at 150. The side effect remains: money moved once. What changed is that the operation now names a thing rather than describing a movement, so a duplicate request lands on the existing thing.
A replace is repeatable for the same reason in a simpler form. If order 7 is {a,b} and the caller sends {x}, the first call leaves {x}; the second writes {x} over {x}. The state after two calls is the state after one. The caller may retry after a timeout without asking the server what happened, because both possible histories, one call or two, end at {x}.
A create where the server chooses the identifier is the opposite case. With zero orders, the first call makes order 101 and the count is one. The second call makes order 102 and the count is two. The state after two calls is not the state after one, so a timeout leaves the caller choosing between a missing order and a duplicate. Moving the identifier to the caller changes the count from 0 to 1 to 1.
Where it stops being true
Safe does not mean the call has no cost or no trace. A read may write a log line, increment a counter, or be slow enough to matter. The property is about the resource state the caller depends on, not about every byte the server writes. If your definition of observable includes those traces, almost nothing is safe.
Idempotency is also defined for the same request repeated. It does not by itself make two simultaneous copies safe. Two requests carrying transfer 7 at the same instant can both look up the record, both find nothing, and both insert unless a uniqueness constraint or a transaction makes one of them lose. The sequential definition says the second call after the first is harmless; it does not say two calls in parallel are harmless. The fix is a uniqueness rule on the identifier, not a retry policy.
There is a class that resists the move entirely: effects outside your system that you cannot inspect, such as an email already sent or a payment already handed to another organisation. For those, repeatability has to come from a record of the attempt on your side, checked before the external effect is triggered.
The practical rule is not 'safe means no side effects'. It is: safe methods may be replayed and cached without asking; idempotent methods may be replayed after a timeout because the state converges; everything else needs a caller supplied key or a query before retry. The classification decides who may retry, not how dangerous the operation sounds.
Worth keeping
- Safe means no observable change; idempotent means a second identical call leaves the state as the first did.
- A delete can be idempotent even though the second call returns a different response, because the property is about state, not the reply.
- Creation becomes idempotent when the caller supplies the identifier, so the second attempt finds the existing thing instead of making another.
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 Choosing the Operations, 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 courseOther questions people ask about this
- 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.
- 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.
- 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.
- Why do I need an idempotency key if my operation is already idempotent?You need the key because repeating an operation safely still leaves the caller unsure what happened, and the key returns the first answer.
- 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.
- 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.