HTTP status codes developers actually need
There are dozens of registered HTTP status codes, but the vast majority of API design and debugging revolves around a couple of dozen of them. Knowing the exact meaning of each — not just "2xx good, 4xx bad" — makes both API design and debugging faster.
2xx: success, but which kind?
- **200 OK** — generic success, body contains the result.
- **201 Created** — the request created a resource; conventionally paired with a `Location` header pointing at the new resource.
- **202 Accepted** — the request was accepted for processing but isn't complete yet (common for async jobs/queues).
- **204 No Content** — success, but there is deliberately no response body (common for `DELETE` or a successful `PUT` that doesn't need to return the updated object).
Returning 200 for everything, including a successful `DELETE`, isn't wrong exactly, but 201/204 carry information that lets clients (and caches) behave more precisely without inspecting the body.
3xx: redirection, and the difference actually matters
- **301 Moved Permanently** — clients and search engines should update their stored URL; safe to cache long-term.
- **302 Found** — a temporary redirect; historically also (incorrectly) implied "resubmit as GET," which browsers preserved for compatibility.
- **307 Temporary Redirect** / **308 Permanent Redirect** — the same semantics as 302/301 but explicitly preserve the original HTTP method and body, removing the GET-conversion ambiguity that 302/301 carry for historical reasons.
If you're redirecting a non-GET request (say, an API client following a redirect after a POST) and need the method preserved, 307/308 are the unambiguous choice.
4xx: the client did something the server won't accept
- **400 Bad Request** — malformed syntax the server can't parse at all (broken JSON, missing required field).
- **401 Unauthorized** — actually means "unauthenticated": no valid credentials were provided. Confusingly named, but that's the RFC definition.
- **403 Forbidden** — the credentials are understood, but the caller isn't allowed to do this. This is the code for "you are who you say you are, but no."
- **404 Not Found** — resource doesn't exist, or the server won't reveal whether it does (sometimes used deliberately to avoid leaking existence of a resource a caller shouldn't know about).
- **409 Conflict** — the request conflicts with the current state of the resource (classic case: two clients editing the same record, or trying to create a resource that already exists with a unique constraint).
- **422 Unprocessable Entity** — syntactically valid request, but semantically invalid (well-formed JSON that fails validation rules). Many APIs use this instead of 400 to distinguish "couldn't parse" from "parsed fine, but the values are wrong."
- **429 Too Many Requests** — rate limited; should be paired with a `Retry-After` header telling the client when to try again.
The 401 vs 403 distinction is the one people get wrong most often: 401 means "log in" (or your token is missing/invalid), 403 means "you're logged in, but this action is not permitted for you."
5xx: the server is the problem
- **500 Internal Server Error** — generic catch-all for an unhandled failure; means something broke server-side that wasn't accounted for.
- **502 Bad Gateway** — a server acting as a proxy/gateway got an invalid response from an upstream server.
- **503 Service Unavailable** — the server is temporarily unable to handle the request (overloaded, in maintenance); ideally paired with `Retry-After`.
- **504 Gateway Timeout** — a proxy/gateway didn't get a response from upstream in time.
502 vs 504 is a useful distinction when debugging a reverse-proxy setup: 502 means upstream responded with garbage or refused the connection outright, 504 means upstream simply never responded in time.
A few practical rules for API design
- Use the most specific code that's accurate; don't default everything to 200/400/500.
- Always return a machine-readable error body alongside 4xx/5xx codes — the status code alone rarely has enough detail for a client to act on.
- Reserve 500 for genuinely unexpected server failures; a validation failure that you anticipated is a 400/422, not a 500.
- Honor `Retry-After` when you send 429/503, and read it when you receive it.
When you're debugging an integration and need the exact meaning of a code you haven't memorized, the [HTTP status codes reference](/tools/network/http-status-codes) is a quicker lookup than searching the RFC.