Retrying a Request
A client on a phone sends POST /notes with the title “Groceries”. The server creates note 42 and sends the 201 response. Before the response reaches the phone, the connection drops. The client waits and reports a timeout.
The client does not know what happened. The request may never have reached the server. The server may still be working on it. Or the server may have created the note, and the response was lost on the way back.
Suppose the client sends the request again. If the first request never arrived at the server, retrying is the right thing to do, and the note gets created. If the note was already created, the server creates a second one. The list now shows two notes titled “Groceries”.
Requests that are safe to repeat
Some requests do no harm when they are sent again. A GET only reads, so sending it a second time changes nothing on the server. HTTP calls such a request safe.
A request that changes data can also be harmless to repeat. A request is idempotent when sending it several times has the same effect on the server as sending it once. Here is what happens when each of our requests is sent a second time:
| Request | Effect of sending it a second time |
|---|---|
GET /notes/42 |
Nothing changes |
PUT /notes/42 |
The note gets the same title and body again |
DELETE /notes/42 |
The note stays deleted |
POST /notes |
A second note is created |
Idempotent does not mean that the responses are the same. The first DELETE gets 204 and the second gets 404, but the server ends up in the same state either way.
HTTP defines GET, PUT, and DELETE as idempotent. POST is not, and PATCH is not in general. A PATCH that meant “add this value to the counter” would change the counter every time it was sent. Our PATCH sets fields to the values in the request, so repeating it is safe, but that comes from how we designed it.
So a client can retry GET, PUT, and DELETE freely, and it can retry our PATCH because it is idempotent. POST needs another mechanism, the idempotency key.
The idempotency key
The server needs a way to recognize that a second POST is another delivery of the first one. Comparing the bodies is not enough, because a person can write two notes with the same title and body on purpose. (I often duplicate a note on purpose and then edit it!) So the client has to send something that identifies which note the request is meant to create.
The client generates a unique value for each new note, and sends it in a header called Idempotency-Key:
POST /notes HTTP/1.1
Host: api.notes.example
Idempotency-Key: 3f7a
Content-Type: application/json
{
"title": "Groceries",
"body": "Milk, eggs, bread"
}
The client uses a new idempotency key for each new note and the same key for every retry of that note.
The server saves the key together with the note it creates. Then it handles a request as follows:
- If the server has not seen the key, it creates a note.
- If the server has seen the key and the title and body are the same, it does not create a note. It sends the same
201response as the first time, with note 42. - If the server has seen the key and the title or body is different, it sends
409 Conflict. Treating that request as a repeat could give the client the wrong note. - If the request has no key, the server creates a note every time.
sequenceDiagram
participant C as Client
participant S as Server
C->>S: POST /notes (Idempotency-Key: 3f7a)
S->>S: Create note 42, save key 3f7a
S--xC: 201 Created (lost)
C->>S: POST /notes (Idempotency-Key: 3f7a)
S->>C: 201 Created, note 42
After the retry, the list shows one note titled “Groceries”.