"There are over 60 standard status codes. You'll meet ~20 in practice. But you don't have to memorize them — the first digit tells you which family it belongs to, and that's usually enough to act."
Five Families That Cover Every Response
Every HTTP status code falls into one of five families, identified by the first digit. Each family is a contract with the client — a polymorphic dispatch. A well-written client never crashes on a status code it's never seen, because the family digit alone tells it what to do:
- 1xx — Informational. "Request received, continuing." Most commonly:
100 Continue(for large uploads),101 Switching Protocols(WebSocket upgrade),103 Early Hints(preload hints before the real response). - 2xx — Success. "It worked." Common:
200 OK(generic success with body),201 Created(new resource — Location header points to it),202 Accepted(async — work queued, not done),204 No Content(success, no body),206 Partial Content(range request). - 3xx — Redirection. "Look elsewhere." Common:
301 Moved Permanently(update bookmarks),302 Found(temporary),304 Not Modified(cache valid, no body),307 Temporary Redirect(method preserved),308 Permanent Redirect(method preserved, permanent). - 4xx — Client Error. "You broke something." Common:
400 Bad Request(malformed),401 Unauthorized(authenticate first),403 Forbidden(authenticated but not allowed),404 Not Found,405 Method Not Allowed,409 Conflict,410 Gone,415 Unsupported Media Type,422 Unprocessable Entity(parsed but failed validation),429 Too Many Requests. - 5xx — Server Error. "I broke something." Common:
500 Internal Server Error(catch-all),502 Bad Gateway(upstream lied),503 Service Unavailable(overloaded / down for maintenance),504 Gateway Timeout(upstream didn't reply in time).
Dispatch on the Family, Refine on the Code
Production HTTP clients use the family digit as the primary branch:
- 2xx → parse the response, return success.
- 3xx → follow the redirect (or surface it).
- 4xx → do NOT retry. The client did something wrong; retrying with the same input will give the same error.
- 5xx → retry with exponential backoff. The server is having a moment; the same request might succeed later.
- 1xx → usually invisible (HTTP libraries handle it for you).
Refinement on the specific code happens for known cases — 401 triggers an auth refresh, 429 reads Retry-After for delay, 409 may trigger a merge — but the family digit is the load-bearing decision.
if status == 200: ... elif status == 201: ... elif status == 204: ..., you've reinvented a switch when you could have used if 200 <= status < 300:. The latter handles every future 2xx (some are still being standardized) without code changes.The Three Most-Confused Pairs
401 vs 403 — 401 means "you have no credentials, or yours are invalid; try logging in." 403 means "your credentials are valid, but you're not allowed to do this." Returning 403 to an unauthenticated user leaks information ("this resource exists, you're just not allowed"); returning 401 when the user IS authenticated is just wrong.
400 vs 422 — 400 means the request was malformed (JSON wouldn't parse, required header missing). 422 means it parsed correctly but failed business validation (email field has "not-an-email"). FastAPI returns 422 by default for Pydantic validation errors, which is correct. Express defaults vary.
301 vs 308 (and 302 vs 307) — All four are redirects, but 301/302 historically allowed clients to change the method (POST → GET on follow). 307/308 explicitly forbid method change. Modern APIs should use 307/308 to avoid the "POST became GET" surprise.
cwkPippa's Status Code Vocabulary
frontend/src/lib/api.ts branches on status_code // 100 first, then refines.