Status codes are HTTP's entire error-communication system compressed into three digits, and they're a favorite interview topic precisely because they reveal whether you understand what's happening between browser and server. This guide covers the codes you'll actually encounter — with the distinctions that trip people up.
The five classes in one look
| Class | Meaning | Who's involved |
|---|---|---|
| 1xx | Informational — request received, keep going | Rarely seen directly |
| 2xx | Success | Everyone's favorite |
| 3xx | Redirection — the resource is elsewhere | Browser follows automatically |
| 4xx | Client error — the request is the problem | Caller must fix |
| 5xx | Server error — the server is the problem | Server owner must fix |
That last distinction — 4xx blames the caller, 5xx blames the server — is the single most important idea. Return the wrong class and every monitoring dashboard, retry policy, and debugging session downstream draws the wrong conclusion.
2xx: more than just 200
- 200 OK — success, response body included.
- 201 Created — success and a new resource exists; the right response to a POST that created something (with a
Locationheader pointing at it). APIs that return 200 for everything are leaving information on the table. - 204 No Content — success with nothing to say; the natural response to a DELETE.
3xx: the redirect family (and the SEO trap)
- 301 Moved Permanently — "update your records." Browsers cache it aggressively, and search engines transfer the old URL's ranking to the new one.
- 302 Found (temporary) — "it's over there for now, keep the old address." No ranking transfer, no assumption of permanence.
- 304 Not Modified — the browser's cached copy is still valid; the server sends headers only, saving the body transfer. This is conditional caching working as designed, not an error.
The trap: using 302 where 301 belongs. When we restructured URLs on this site, every old URL got an explicit 301 — a 302 would have told Google to keep the dead URLs indexed indefinitely. The reverse trap is worse: a 301 you later regret is nearly impossible to un-teach to browsers that cached it. Rule of thumb — 301 only when you're sure, and test redirects with curl -I before shipping them.
4xx: the precise vocabulary of "you did it wrong"
- 400 Bad Request — malformed input: invalid JSON, missing required fields.
- 401 Unauthorized — misnamed; it really means unauthenticated: "I don't know who you are." Missing or expired credentials.
- 403 Forbidden — authenticated but not allowed: "I know exactly who you are, and no." The 401-vs-403 distinction is a perennial interview question because using them correctly requires understanding authentication vs authorization.
- 404 Not Found — no such resource. Also used deliberately instead of 403 when confirming a resource exists would itself leak information (private repos on GitHub return 404, not 403, to strangers).
- 422 Unprocessable Entity — syntactically valid but semantically wrong: well-formed JSON whose email field isn't an email. Laravel's validation errors use it, distinguishing "your JSON is broken" (400) from "your data is wrong" (422).
- 429 Too Many Requests — rate limited; well-behaved clients read the
Retry-Afterheader and back off.
5xx: when it's not their fault
- 500 Internal Server Error — the generic "something threw an exception." If your API returns 500 for bad user input, you're mislabeling caller errors as server bugs — and your error monitoring will page you for typos in other people's requests.
- 502 Bad Gateway — a proxy (nginx, a load balancer, a CDN) reached your backend and got garbage or nothing. Classic cause: the application process is down while the web server is up.
- 503 Service Unavailable — deliberately down: maintenance mode, overload shedding. Laravel's
artisan downreturns this. - 504 Gateway Timeout — the proxy gave up waiting. Usually a slow query or an external call without a timeout, surfacing at the infrastructure layer.
Debugging heuristic for the 502/503/504 family: the problem is almost never in the proxy that reported it — the proxy is the messenger. Look at what sits behind it.
Choosing codes in your own APIs
Three habits produce APIs people trust: use the most specific correct code (401 vs 403, 400 vs 422 — clients build logic on these distinctions); never return 200 with an error in the body — {"status": "error"} under HTTP 200 breaks every generic client, cache, and monitor that reasonably trusts the status line; and pair machine-readable codes with human-readable bodies, so the status says what kind of problem and the body says which one exactly. Get these right and your API documents itself one response at a time.