# HTTP Status Codes
Status codes tell the client what happened to its request in one number. This sheet groups them by class and flags the ones that get misused in real APIs — returning 200 on failure is the single biggest offender.
Quick reference
1xx — Informational
| Code |
Name |
Meaning |
| 100 |
Continue |
Client should send the rest of the request body |
| 101 |
Switching Protocols |
Server agrees to switch protocol (used for WebSocket upgrade) |
2xx — Success
| Code |
Name |
Meaning |
| 200 |
OK |
Standard success |
| 201 |
Created |
Resource created (include a `Location` header pointing to it) |
| 202 |
Accepted |
Request accepted for async processing, not yet complete |
| 204 |
No Content |
Success, no response body (common on `DELETE`) |
| 206 |
Partial Content |
Range request fulfilled (video/audio streaming, resumable downloads) |
3xx — Redirection
| Code |
Name |
Meaning |
| 301 |
Moved Permanently |
Resource has a new URL permanently; caches/search engines update their link |
| 302 |
Found |
Temporary redirect (historically often misused for what should be 303/307) |
| 303 |
See Other |
Redirect after a POST — client should GET the new URL |
| 304 |
Not Modified |
Cached version is still valid (`If-None-Match`/`If-Modified-Since`) |
| 307 |
Temporary Redirect |
Like 302 but guarantees the method/body are preserved |
| 308 |
Permanent Redirect |
Like 301 but guarantees the method/body are preserved |
4xx — Client error
| Code |
Name |
Meaning |
| 400 |
Bad Request |
Malformed syntax — the server can't even parse the request |
| 401 |
Unauthorized |
Not authenticated (despite the name, this is about *auth*, not permission) |
| 403 |
Forbidden |
Authenticated, but not allowed to access this resource |
| 404 |
Not Found |
Resource doesn't exist (or the server won't say why) |
| 405 |
Method Not Allowed |
Right URL, wrong HTTP method — include an `Allow` header |
| 409 |
Conflict |
Request conflicts with current state (e.g. version mismatch, duplicate) |
| 410 |
Gone |
Resource existed but was intentionally, permanently removed |
| 422 |
Unprocessable Entity |
Syntactically valid but semantically wrong (failed validation) |
| 429 |
Too Many Requests |
Rate limited — should include a `Retry-After` header |
5xx — Server error
| Code |
Name |
Meaning |
| 500 |
Internal Server Error |
Generic catch-all — something broke server-side |
| 502 |
Bad Gateway |
Upstream/proxy got an invalid response from the origin server |
| 503 |
Service Unavailable |
Server temporarily can't handle the request (overload, maintenance) |
| 504 |
Gateway Timeout |
Upstream/proxy didn't get a response from the origin in time |
Common patterns
Standard API error envelope
{
"error": {
"code": "rate_limited",
"message": "Too many requests, retry after 30s",
"request_id": "req_8f2a1c"
}
}
Pair this body with the matching status code — 429 here, not 200 with an "error" field buried in it.
Correct redirect after a form POST
POST /orders -> 303 See Other
Location: /orders/42
Using 303 (not 302) after a POST guarantees the browser issues a GET for the redirect target instead of potentially re-submitting the POST body.
Conditional GET with caching
GET /report.pdf
If-None-Match: "33a64df5"
304 Not Modified <- no body sent, client uses its cached copy
Pitfalls
- Returning
200 OK with an error in the body: this breaks every generic HTTP client, monitoring tool, and cache that inspects the status line — they'll treat a failed request as a success. The status code is the contract; never make callers parse the body to learn if something failed.
401 vs 403 mixed up constantly: 401 Unauthorized means "I don't know who you are" (missing/invalid credentials); 403 Forbidden means "I know who you are, and you're not allowed." Returning 403 for an expired token trains clients to retry uselessly instead of re-authenticating.
404 used for authorization failures: some APIs return 404 instead of 403 to avoid leaking that a resource exists. That's a deliberate security tradeoff, not a mistake — but document it, because it looks like a bug otherwise.
302 changing the method on redirect: older HTTP clients (and some still today) turn a 302-redirected POST into a GET at the new location, silently dropping the request body. Use 307/308 when you need the method and body preserved.
- No
Retry-After on 429/503: without it, well-behaved clients don't know how long to back off and will hammer the endpoint immediately, defeating the point of the rate limit.
Related ZipKit tools
- JWT Decoder — inspect the claims behind a
401/403 to see exactly why an API rejected a token.
- CSP Header Builder — build the response headers that usually ship alongside a hardened API's status codes.
Related cheatsheets