HTTP status codes developers actually need

7 min read
http
networking

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.

Tools from this article

← All articles