HTTP Status Codes Explained: Every Code You Will Actually Meet
The dividing line: 4xx means the request was wrong (fix the caller), 5xx means the server failed (fix the service). Get that split right and most status-code bugs resolve themselves.
The Reference Table
| Code | Name | Means | Typical cause / fix |
|---|---|---|---|
| 200 | OK | Success with body | — |
| 201 | Created | Resource created | Return Location header with the new resource URL |
| 204 | No Content | Success, no body | Correct for DELETE and update-without-response |
| 206 | Partial Content | Range request served | Video streaming, resumable downloads |
| 301 | Moved Permanently | New URL forever | HTTP→HTTPS, domain migration; browsers cache it hard |
| 302 / 307 | Found / Temporary Redirect | Moved temporarily | 307 preserves the HTTP method; 302 historically does not |
| 304 | Not Modified | Your cached copy is current | Normal ETag/If-None-Match flow, not an error |
| 308 | Permanent Redirect | Like 301 but method-preserving | API version moves |
| 400 | Bad Request | Malformed request | Invalid JSON, missing required field, bad query param format |
| 401 | Unauthorized | Not authenticated | Missing/expired token. Name is wrong — it means "unauthenticated" |
| 403 | Forbidden | Authenticated but not allowed | Valid token, insufficient role/scope |
| 404 | Not Found | No such resource | Wrong URL, deleted resource, or deliberately hidden resource |
| 405 | Method Not Allowed | Wrong HTTP verb | POSTing to a GET-only endpoint; check the Allow header |
| 408 | Request Timeout | Client too slow | Rare; usually seen through proxies |
| 409 | Conflict | State conflict | Duplicate create, concurrent edit; safe to retry after re-reading |
| 410 | Gone | Deliberately removed | Stronger than 404; tells indexers to drop the URL |
| 413 | Payload Too Large | Body exceeds limit | Raise the limit or chunk the upload |
| 415 | Unsupported Media Type | Wrong Content-Type | Send application/json when the API expects it |
| 422 | Unprocessable Entity | Well-formed but semantically invalid | Validation errors; the request parsed but the values are wrong |
| 429 | Too Many Requests | Rate limited | Back off; honor Retry-After |
| 500 | Internal Server Error | Unhandled server exception | Check server logs — the response intentionally hides the cause |
| 502 | Bad Gateway | Upstream sent garbage or died | App crashed behind the proxy; check upstream health |
| 503 | Service Unavailable | Server can't handle it right now | Overload or maintenance; often has Retry-After |
| 504 | Gateway Timeout | Upstream too slow | Long query, deadlock, or an upstream that hangs |
The 401 vs 403 Confusion, Settled
401 Unauthorized is named badly — it means unauthenticated: the server does not know who you are (no token, expired token, malformed header). 403 Forbidden means the server knows exactly who you are and you still cannot do this (wrong role, missing scope, IP blocked). Practical consequence: a 401 should trigger a login/refresh flow in clients; a 403 must not — retrying with the same credentials will never succeed. The full 401 vs 403 guide covers the debugging recipes.
The Codes That Indicate You (the Client) Erred
400, 404, 405, 413, 415, 422 and friends mean the request itself was the problem. The debugging question is never "why is the server broken" but "what exactly did I send?" The fastest tool is the raw request: curl with -v shows headers and body as sent, before any HTTP library massages them.
curl -v -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"name": "x"}'Common mismatches: Content-Type says form-encoded while the body is JSON (→ 415 or 400), trailing slash redirect on POST (→ 405 after the redirect), and empty body on a required-field endpoint (→ 400/422 depending on the framework).
The Codes That Indicate the Server Erred
500 means an exception escaped your handler — the fix is in the server log, always. 502/504 mean an intermediary (nginx, Cloudflare, an API gateway) could not get a good answer from the app: 502 usually means the app process is dead or crashed mid-request, 504 means it is alive but slower than the proxy's timeout. The distinction matters: a 502 is a health/lifecycle problem, a 504 is a performance problem. The 500 guide walks through the log-to-cause path.
Choosing Codes When Building an API
Rules that prevent arguments in code review: (1) created → 201 with Location; (2) validation failure → 422 (or 400, but pick one convention); (3) auth missing → 401, insufficient permission → 403; (4) duplicate resource → 409; (5) rate limit → 429 with Retry-After; (6) never return 200 with an error body — clients and monitors will both lie to you. The last one is the most expensive anti-pattern: it defeats every status-code-based alert you will ever write.