Logics Guru

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.

4 min read 2 views

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#

CodeNameMeaning
100ContinueHeaders received; the client should send the body. Used with Expect: 100-continue to avoid uploading a large body that would be rejected.
101Switching ProtocolsThe server is changing protocol at the client's request — this is how a WebSocket handshake completes.
103Early HintsSends Link headers before the final response so the browser can start preloading. Real performance win, still underused.

2xx — Success#

CodeNameWhen to use it
200OKThe default success. The response body carries the result.
201CreatedA new resource exists as a result of this request. Include a Location header pointing at it.
202AcceptedThe request was valid and queued, but is not done. The correct answer for anything asynchronous.
204No ContentSuccess with deliberately no body. The right answer to a DELETE, and to a PUT that returns nothing.
206Partial ContentA 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.

CodeNameWhen to use it
301Moved PermanentlyThis 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.
302FoundA temporary redirect. Use it whenever the destination might change, or the redirect might be withdrawn.
303See OtherRedirect 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.
304Not ModifiedThe client's cached copy is still valid. Sent in response to If-None-Match or If-Modified-Since, with no body.
307Temporary RedirectLike 302, but the method and body must be preserved. Use it when redirecting a POST that should stay a POST.
308Permanent RedirectLike 301, but preserves the method.

4xx — Client errors#

CodeNameWhen to use it
400Bad RequestThe request is malformed — unparseable JSON, a missing required field. Not for a request that is well-formed but semantically wrong.
401UnauthorizedMisnamed: it means unauthenticated. No credentials, or bad ones. Must include a WWW-Authenticate header.
403ForbiddenWe know who you are and you still may not. Re-authenticating will not help.
404Not FoundNo 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.
405Method Not AllowedThe URL exists, this verb does not. Must include an Allow header listing the ones that do.
409ConflictThe request conflicts with current state — a duplicate unique value, or an edit against a stale version.
410GoneIt existed and is permanently removed. More informative than 404, and crawlers drop the URL faster.
413Content Too LargeThe body exceeds what the server will accept.
415Unsupported Media TypeThe Content-Type is not one this endpoint handles.
422Unprocessable ContentWell-formed, but failed validation. This — not 400 — is the right code for a form that parsed correctly and contains invalid values.
429Too Many RequestsRate limited. Always send Retry-After, or clients will simply retry immediately and make it worse.

5xx — Server errors#

CodeNameWhen to use it
500Internal Server ErrorSomething broke and it is our fault. Never leak a stack trace in the body.
501Not ImplementedThe server does not support this method at all — distinct from 405, which is per-URL.
502Bad GatewayAn upstream server returned something invalid. Classic reverse-proxy answer when the application behind it has died.
503Service UnavailableTemporarily down — maintenance or overload. Send Retry-After; search engines treat this correctly and will come back.
504Gateway TimeoutAn 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.

Mustasim Ali

Mustasim Ali

Senior Software Engineer & Technical Lead

Full-stack engineer working in PHP and Laravel since 2019. I lead a development team building web and mobile products, and spend most of my time in Laravel, Node.js, Vue and React against MySQL and MongoDB. Logics Guru is where I write up the things I had to work out the hard way — the architecture decisions, the debugging sessions, and the small utilities I kept rebuilding until I put them somewhere permanent. Everything here is what I actually use.