🐍 Backend with Python · Chapter 2
HTTP properly: methods, status codes, headers, idempotency
Networks drop replies, so clients retry. Whether that retry books one cab or two is decided by the HTTP method you picked and the status codes you send.
Study this lesson free in the app →
The basics
HTTP is the agreed language between client and backend. Four parts of it carry meaning that every client, proxy and library relies on.
The problem it solves: clients, caches and retry libraries you'll never see make decisions from the method and the status code alone, so if those are wrong, retries double-charge people and caches serve stale data.
The mental model: the method is the verb (read, create, replace, delete), the status code is the outcome in one number, and headers are the envelope notes (what format, who's asking, where the new thing lives).
Words you must know 5
- Method
GETreads,POSTcreates or triggers,PUTreplaces at a known address,PATCHchanges part,DELETEremoves.- Safe
- the method asks for no change on the server:
GET,HEAD,OPTIONS. - Idempotent
- sending it 5 times leaves the server the same as sending it once: all safe methods plus
PUTandDELETE.POSTandPATCHare not. - Status code
2xxit worked,3xxlook elsewhere,4xxyour request is wrong,5xxthe server failed.- Header
- a
Name: valueline of metadata, likeContent-Type: application/jsonorLocation: /orders/981.
Explained
Friday, 7:52 PM, Bengaluru metro
Ananya pays ₹1,240 for groceries as the train enters a tunnel. The app sends POST /payments. The server charges her bank in 900 ms and writes the row. The reply never arrives: the network is gone.
The app waits 10 seconds, sees a timeout, and does what retry libraries do: sends the same POST again. The server, seeing a brand-new request, charges her again. Two debits, one order.
Nobody wrote a bug in the handler. The bug is that the app retried a method that isn't idempotent, and the server gave it no way to recognise a repeat. The fix is ten lines and a header, and it's in this chapter.
Two ways to order at a chai stall
You shout "Bhaiya, make it 2 chai for table 4." The boy didn't hear, so you shout it again. Table 4 still gets 2 chai. That is PUT: you state the final result, so repeating it is harmless.
Your friend shouts "2 more chai for table 4!" twice. Now table 4 has 4 extra chai and a bigger bill. That is POST: each message is a new action.
"Cancel table 4's order" twice? Still cancelled, nothing more happens. That is DELETE.
"What's on the menu?" changes nothing at all, however many times you ask. That is GET: safe, not just idempotent.
Same words, same table. Whether shouting twice is a problem depends entirely on which kind of sentence you used.
Methods, per RFC 9110
| Method | Use it to | Safe | Idempotent | Body | | --- | --- | --- | --- | --- | | GET | read a resource | ✅ | ✅ | none | | HEAD | GET without the body (size, freshness) | ✅ | ✅ | none | | OPTIONS | ask what's allowed (CORS preflight) | ✅ | ✅ | none | | POST | create a child, or trigger an action | ❌ | ❌ | yes | | PUT | create or replace at this exact URL | ❌ | ✅ | yes | | PATCH | change part of a resource | ❌ | ❌ | yes | | DELETE | remove | ❌ | ✅ | rarely |
Safe = the client asks for no state change, so crawlers, link previewers and prefetchers will happily call it. Idempotent = the effect on the server of N identical requests equals the effect of one. It says nothing about the response being identical: the second DELETE may well get 404.
Retry rule that follows: a client or proxy may automatically retry idempotent requests after a dropped connection. POST and PATCH need an idempotency key first.
Status codes you'll actually send
200 OK— here's the result ·201 Created— made it; put its URL inLocation·204 No Content— done, no body (a typicalDELETE)301/308moved for good ·302/307moved for now (307/308keep the method) ·303 See Other— "go GET this" after a POST ·304 Not Modified— your cached copy is fine400 Bad Request— malformed request ·422 Unprocessable Content— well-formed but invalid values (FastAPI's choice for every validation failure, even broken JSON)401 Unauthorized— we don't know who you are (no or bad credentials) ·403 Forbidden— we know who you are, and the answer is no404 Not Found— nothing here (or we won't say) ·410 Gone— it existed and was removed on purpose, don't come back405 Method Not Allowed(+Allowheader) ·409 Conflict— clashes with current state, e.g. email already registered ·429 Too Many Requests(+Retry-After)500your code threw ·502 Bad Gateway— the proxy got a bad/no answer from your app ·503 Service Unavailable— overloaded or in maintenance ·504 Gateway Timeout— the proxy gave up waiting
The rule: 4xx = the client should change the request; 5xx = retrying the same request later might work.
Headers: the notes on the envelope
POST /orders HTTP/1.1 ← method, path, version
Host: api.chaiwala.in ← which site (one IP, many sites)
Content-Type: application/json ← how to read MY body
Accept: application/json ← what I can read back
Authorization: Bearer eyJhbGciOi… ← who I am (be-008)
Idempotency-Key: 4b1e-9c2d-k7f3a ← "this is the same order if I retry"
Content-Length: 26
← blank line = headers over
{"item": "chai", "qty": 2}
HTTP/1.1 201 Created ← outcome first
Location: /orders/981 ← where the new thing lives
Content-Type: application/json
Content-Length: 37
{"id": 981, "item": "chai", "qty": 2}Content-Type describes the body of the message it's in. Accept describes what the sender wants back. Location on a 201 saves the client a guess. Idempotency-Key isn't in RFC 9110: it's an IETF draft that Stripe and most payment APIs already use.
Predict: three PUTs, three POSTs
carts = {}
@app.put("/carts/{cid}/items/chai")
def set_chai(cid: int, qty: int):
carts.setdefault(cid, {})["chai"] = qty # set, not add
return carts[cid]
@app.post("/carts/{cid}/items/chai")
def add_chai(cid: int, qty: int):
cart = carts.setdefault(cid, {})
cart["chai"] = cart.get("chai", 0) + qty # add
return cart
for _ in range(3): r = client.put("/carts/1/items/chai?qty=2")
print(r.json())
for _ in range(3): r = client.post("/carts/2/items/chai?qty=2")
print(r.json())How an idempotency key stops the double charge
In production seen_keys is a database table with a unique constraint on the key, entries expire after about 24 hours, and two requests with the same key arriving at once must not both pass line 8 (be-006 covers the transaction that guarantees it).
HTTP mistakes that reach production
- GET with side effects.
GET /unsubscribe?u=42in an email: corporate mail scanners (Microsoft Defender's Safe Links, for one) fetch every link to check it, and users get unsubscribed without clicking. Changes go inPOST. - 200 for everything. Errors as
200 {"ok": false}make your 5xx dashboard read zero during an outage. - 401 when you mean 403. 401 tells the client "log in again"; a logged-in user without permission then loops through the login screen forever.
- Retrying POST blindly. Every HTTP client library's retry option should be off for POST unless you send an idempotency key.
201withoutLocation, or204with a body. A 204 has no body by definition, so the new id you tucked in there never arrives.- Missing
Content-Type. Send{"qty": 2}withoutContent-Type: application/jsonand FastAPI won't parse it: you get422with"Input should be a valid dictionary or object to extract fields from".
Words to know
| Safe method | A method whose semantics request no state change on the server: GET, HEAD, OPTIONS, TRACE. |
|---|---|
| Idempotent method | N identical requests have the same intended effect on the server as one: the safe methods plus PUT and DELETE. |
| Idempotency key | A unique id the client sends with a non-idempotent request so the server can detect a retry and replay the first response. |
| 201 Created | The request made a new resource; the Location header should give its URL. |
| 401 vs 403 | 401: no valid credentials, authenticate. 403: credentials understood, permission refused. |
| 422 Unprocessable Content | The request is well-formed but its values fail validation. FastAPI's default for validation errors. |
| Location header | The URL of the resource a 201 created, or of the target of a 3xx redirect. |
| Retry-After | Header on 429 or 503 telling the client how long to wait before trying again. |
In two minutes
- The method is a promise: GET reads, POST creates or acts, PUT replaces at a known URL, PATCH edits part, DELETE removes.
- Safe methods (GET, HEAD, OPTIONS) request no change; idempotent ones (those plus PUT, DELETE) can be repeated with the same effect.
- Idempotency is about the server's state, not the response: a second DELETE can return 404 and still be idempotent.
- Networks drop replies, so clients retry; a retried POST double-creates unless the server dedupes with an idempotency key.
- Status codes: 2xx worked, 3xx look elsewhere, 4xx the client must change something, 5xx the server failed and a retry may work.
- 401 means unauthenticated, 403 means authenticated but not allowed; 201 should carry a Location header.
- Headers carry the metadata: Content-Type, Accept, Authorization, Location, Retry-After, Idempotency-Key.
Test yourself
1. What does it mean for an HTTP method to be idempotent?
- Repeating the request returns byte-identical responses
- Repeating the request N times leaves the server in the same state as sending it once
- The request changes nothing on the server
- The response can be cached
Show the answer
Repeating the request N times leaves the server in the same state as sending it once — Idempotency is about effect on server state. 'Changes nothing' is the stronger property, safe. Identical responses aren't required: DELETE then DELETE gives 204 then 404.
2. Which methods does RFC 9110 define as safe?
- GET, HEAD, OPTIONS, TRACE
- GET, PUT, DELETE
- GET and POST
- Every method except DELETE
Show the answer
GET, HEAD, OPTIONS, TRACE — Safe means the client requests no state change. PUT and DELETE are idempotent but not safe: they change state, just repeatably.
3. Why is it fine for a client to auto-retry a timed-out PUT but dangerous to auto-retry a POST?
- PUT requests are smaller
- Servers ignore duplicate PUTs automatically
- POST bodies are encrypted
- PUT replaces the resource at a known URL, so a repeat lands on the same state; a repeated POST can create a second resource
Show the answer
PUT replaces the resource at a known URL, so a repeat lands on the same state; a repeated POST can create a second resource — The client can't know whether the first request was applied. With PUT it doesn't matter; with POST you may get two orders. Servers don't dedupe anything 'automatically': that's what idempotency keys are for.
4. What is the difference between 401 and 403?
- 401 is for APIs, 403 for web pages
- 401 is temporary, 403 is permanent
- 401: we don't know who you are (missing or bad credentials); 403: we know, and you're not allowed
- They mean the same; pick either
Show the answer
401: we don't know who you are (missing or bad credentials); 403: we know, and you're not allowed — Despite its name, 401 'Unauthorized' is about authentication, and it should carry a WWW-Authenticate header. Sending 401 to a logged-in user without permission makes clients re-login in a loop.
5. What happens when a client sends POST /menu to a FastAPI app that only defines @app.get("/menu")?
- 404 Not Found
- 405 Method Not Allowed, with an `Allow: GET` header
- The GET handler runs
- 422 Unprocessable Content
Show the answer
405 Method Not Allowed, with an `Allow: GET` header — The path exists, the method doesn't. FastAPI answers 405 with Allow: GET and body {"detail":"Method Not Allowed"}. 404 would wrongly claim nothing lives at that path.
6. How should a successful POST /orders that created order 981 respond?
- 201 Created with `Location: /orders/981` and the order in the body
- 200 with an empty body
- 204 No Content with the id in the body
- 302 redirect to /orders
Show the answer
201 Created with `Location: /orders/981` and the order in the body — 201 says something new exists, and Location says where. A 204 must not have a body, so the id would be lost. 200 isn't wrong, but it throws away information the client can use.
There are 10 questions, 3 puzzles and 5 recall cards for this lesson in the app — the recall cards come back on later days, just before you would forget.
Start this lesson →