HTTP Status Codes: A Complete Reference
Every HTTP status code worth knowing, what it actually means, and when to return it rather than something close to it.
Status codes are the part of HTTP most often got approximately right. Returning 200 with an error in the body, or 404 when you meant 403, works until something automated depends on it — a cache, a retry policy, a crawler, a monitoring alert. This is the full set worth knowing and the distinctions that matter.
1xx — Informational#
| Code | Name | Meaning |
|---|---|---|
100 | Continue | Headers received; the client should send the body. Used with Expect: 100-continue to avoid uploading a large body that would be rejected. |
101 | Switching Protocols | The server is changing protocol at the client's request — this is how a WebSocket handshake completes. |
103 | Early Hints | Sends Link headers before the final response so the browser can start preloading. Real performance win, still underused. |
2xx — Success#
| Code | Name | When to use it |
|---|---|---|
200 | OK | The default success. The response body carries the result. |
201 | Created | A new resource exists as a result of this request. Include a Location header pointing at it. |
202 | Accepted | The request was valid and queued, but is not done. The correct answer for anything asynchronous. |
204 | No Content | Success with deliberately no body. The right answer to a DELETE, and to a PUT that returns nothing. |
206 | Partial Content | A range request succeeded. This is what makes video seeking and resumable downloads work. |
3xx — Redirection#
The distinction that matters most on the whole page is 301 against 302.
| Code | Name | When to use it |
|---|---|---|
301 | Moved Permanently | This URL will never serve this resource again. Browsers cache it aggressively and search engines transfer ranking to the target. Hard to undo — a visitor who followed it once may not ask your server again for a long time. |
302 | Found | A temporary redirect. Use it whenever the destination might change, or the redirect might be withdrawn. |
303 | See Other | Redirect to a different resource, always fetched with GET. This is the correct answer after a form POST — the POST-Redirect-GET pattern that stops a refresh resubmitting. |
304 | Not Modified | The client's cached copy is still valid. Sent in response to If-None-Match or If-Modified-Since, with no body. |
307 | Temporary Redirect | Like 302, but the method and body must be preserved. Use it when redirecting a POST that should stay a POST. |
308 | Permanent Redirect | Like 301, but preserves the method. |
4xx — Client errors#
| Code | Name | When to use it |
|---|---|---|
400 | Bad Request | The request is malformed — unparseable JSON, a missing required field. Not for a request that is well-formed but semantically wrong. |
401 | Unauthorized | Misnamed: it means unauthenticated. No credentials, or bad ones. Must include a WWW-Authenticate header. |
403 | Forbidden | We know who you are and you still may not. Re-authenticating will not help. |
404 | Not Found | No such resource. Also the right answer when you do not want to confirm a resource exists at all — returning 403 tells an attacker they found something real. |
405 | Method Not Allowed | The URL exists, this verb does not. Must include an Allow header listing the ones that do. |
409 | Conflict | The request conflicts with current state — a duplicate unique value, or an edit against a stale version. |
410 | Gone | It existed and is permanently removed. More informative than 404, and crawlers drop the URL faster. |
413 | Content Too Large | The body exceeds what the server will accept. |
415 | Unsupported Media Type | The Content-Type is not one this endpoint handles. |
422 | Unprocessable Content | Well-formed, but failed validation. This — not 400 — is the right code for a form that parsed correctly and contains invalid values. |
429 | Too Many Requests | Rate limited. Always send Retry-After, or clients will simply retry immediately and make it worse. |
5xx — Server errors#
| Code | Name | When to use it |
|---|---|---|
500 | Internal Server Error | Something broke and it is our fault. Never leak a stack trace in the body. |
501 | Not Implemented | The server does not support this method at all — distinct from 405, which is per-URL. |
502 | Bad Gateway | An upstream server returned something invalid. Classic reverse-proxy answer when the application behind it has died. |
503 | Service Unavailable | Temporarily down — maintenance or overload. Send Retry-After; search engines treat this correctly and will come back. |
504 | Gateway Timeout | An upstream server did not answer in time. |
The four distinctions worth memorising#
- 401 against 403 — unauthenticated against unauthorised. Different fixes.
- 400 against 422 — could not parse it against parsed it and it was invalid.
- 301 against 302 — permanent is cached by the browser and is very hard to take back.
- 404 against 410 — might return one day against definitively gone.
Two rules that prevent most problems#
Never return 200 with an error inside. Every cache, retry policy, alert and crawler between you and the client reads the status line, not your JSON. A 200 containing {"error": "not found"} gets cached as a success.
Always send Retry-After with 429 and 503. Without it, well-behaved clients guess, and badly-behaved ones hammer you at exactly the moment you are least able to take it.