"The family digit gets you 80% of the way. The remaining 20% — the difference between 201 and 204, 301 and 308, 401 and 403, 400 and 422 — is what separates a tolerable API from a respected one."
2xx — Success Has Five Shapes
All 2xx codes mean the request worked. The specific code tells the client how:
- 200 OK — generic success. Response body contains the result. Use for GET, idempotent updates that return the new state, and most everyday success.
- 201 Created — the request created a new resource. Response MUST include a
Locationheader pointing at the new resource's URL. Often the body also carries the new resource. Use for POST that creates and PUT that creates (when the resource didn't exist). - 202 Accepted — the request was accepted for processing but isn't done yet. The body or
Locationheader should point at a status URL the client can poll. Use for long-running async operations — Stripe payouts, video transcoding, council finalization. - 204 No Content — success, explicitly no body. Common after DELETE and after PUT/PATCH that don't return the updated resource. Clients that always call
response.json()will crash on 204. - 206 Partial Content — response to a Range request. Used by video streaming, resumable downloads, big-file APIs. The body is a byte range of the resource, not the whole thing.
3xx — Redirects Differ on Method Preservation
The 3xx redirect pairs are the source of an entire class of bugs: a POST gets redirected, and the redirected request becomes a GET (because that's what 301/302 historically allowed). The fix: use the method-preserving codes.
- 301 Moved Permanently — "this URL is gone forever; use the one in Location." Clients update their bookmarks. Historically allowed method-change on follow (POST → GET); modern guidance says don't.
- 308 Permanent Redirect — same as 301 but EXPLICITLY method-preserving. POST stays POST. Use this for permanent moves of any non-GET endpoint.
- 302 Found — temporary redirect. Historically allowed method-change.
- 307 Temporary Redirect — same as 302 but explicitly method-preserving. POST stays POST. Use this for temporary redirects of any non-GET endpoint.
- 304 Not Modified — the cached copy is still valid; no body returned. Conditional-request response (Track 2 lesson 4).
4xx — The Confused-Pair Hall of Fame
Four pairs that cost careers:
401 Unauthorized vs 403 Forbidden. 401 = "who are you?" — send credentials. 403 = "I know who you are, and no." Sending credentials won't help. Returning 403 to an unauthenticated user leaks resource existence; returning 401 when authentication succeeded just lies. WWW-Authenticate header should accompany 401 to tell the client HOW to authenticate.
400 Bad Request vs 422 Unprocessable Entity. 400 = "I couldn't parse your request." Wrong JSON, missing required header, malformed URL. 422 = "I parsed it fine, but the contents violated a business rule." Email field with "not-an-email," negative quantity. FastAPI/Pydantic gets this right; many hand-rolled APIs collapse both into 400 and lose the diagnostic signal.
404 Not Found vs 410 Gone. 404 = "I don't know if this ever existed or might exist in the future." 410 = "this existed, has been deleted, and is never coming back — stop checking." 410 lets clients prune their indexes; 404 doesn't carry that signal.
409 Conflict. The request can't be applied because of the resource's current state. Most common cases: PUT with stale ETag (use 412 Precondition Failed instead when you have the ETag), DELETE on a resource with active dependents, POST that violates a uniqueness constraint.
5xx — All Mean Retry, But Differently
- 500 Internal Server Error — the server caught an exception it didn't expect. Retry with backoff; may resolve on next deploy.
- 502 Bad Gateway — your server is a gateway/proxy and the upstream gave a malformed response. Retry; the upstream is having a moment.
- 503 Service Unavailable — the server is overloaded or down for maintenance. SHOULD include
Retry-Afterwith a delay. Respect it. - 504 Gateway Timeout — your server is a gateway and the upstream didn't reply in time. Retry; the upstream may have just been slow.
cwkPippa's Status Code Realities
Location for new conversations, 202 for async council finalization (the client polls for completion), 204 for archive-folder DELETE, 400 for malformed chat payloads, 401 for missing auth, 403 for non-admin trying to hit admin routes, 422 for Pydantic field validation, 429 with Retry-After when the OpenAI/Anthropic upstream rate-limits us, 500 for uncaught exceptions, and 503 with Retry-After when a backend brain (Codex/Gemini) is down. Every one of these decisions came from a real production moment.