The first digit is the whole story
Every status code’s category is set by its leading digit, and clients should branch on the category first, the specific code second:
| Class | Meaning | Client should… |
|---|---|---|
| 1xx | Informational | Keep waiting (rarely surfaced) |
| 2xx | Success | Proceed |
| 3xx | Redirection | Follow, or revalidate cache |
| 4xx | Client error | Fix the request; don’t blindly retry |
| 5xx | Server error | Retry with backoff; the request may be fine |
That 4xx/5xx split drives retry logic: hammering a server after a 400 just wastes both sides’ resources, while a 503 genuinely may succeed on the next attempt.
2xx has more than 200
Reaching for 200 every time throws away information clients could use:
- 201 Created — a resource was made; pair it with a
Locationheader. - 202 Accepted — you’ve queued the work but haven’t done it yet (async jobs). The client should poll a status URL.
- 204 No Content — success, deliberately empty body. Ideal for
DELETEand forPUTupdates where echoing the object is pointless. - 206 Partial Content — you’re serving a byte range (video scrubbing, resumable downloads).
3xx: the method-preservation gotcha
The redirect codes split on a subtle but important axis — whether the HTTP method survives the redirect:
| Code | Permanent? | Preserves method? |
|---|---|---|
| 301 | Yes | No — may rewrite POST → GET |
| 308 | Yes | Yes — POST stays POST |
| 302 | No | No — historically rewrites to GET |
| 307 | No | Yes — method preserved |
| 304 | n/a | ”Not Modified” — use your cached copy |
Use 301 for plain URL moves (HTTP→HTTPS, domain migration) where turning a POST into a GET is harmless. Use 307/308 when a redirected request must remain a POST — for example, an API endpoint that moved but still receives form submissions. Choosing 302 for an API redirect is how POST bodies silently vanish.
The codes that signal a mature API
A few well-placed codes make an integration noticeably more pleasant:
- 429 Too Many Requests with a
Retry-Afterheader tells well-behaved clients exactly how long to back off, instead of leaving them to guess. - 409 Conflict for a duplicate create or a lost-update edit, rather than a vague 400.
- 410 Gone for resources you’ve permanently retired — search engines de-index a 410 faster than a 404.
- 503 Service Unavailable with
Retry-Afterduring maintenance, so clients and load balancers don’t treat a planned window as a hard failure.
The opposite anti-pattern — returning 200 OK with an {"error": ...} body — defeats every layer that reads status codes (caches, proxies, client libraries). Let the status line carry the outcome and the body carry the detail.