InTab

Blog

HTTP status codes explained: what to return and when

By Uxair Ijaz · 2026-11-06 · 10 min read

Status codes are the part of HTTP everyone uses and few use precisely. The difference between 400 and 422, or 401 and 403, decides whether a mobile app shows the right error, whether a monitoring alert fires, and whether a retry loop melts your production database. This guide covers every code worth using in API work, the mistakes even experienced teams make, and what clients do with each one.

The five classes

The first digit is the class:

Class Meaning Who acts
1xx Informational — keep the connection open Frameworks, not you
2xx Success Client proceeds
3xx Redirect — go elsewhere Client (and caches for 301/308)
4xx Client error — the request was wrong Client must change something
5xx Server error — the server failed Client may retry later; you get paged

The class boundary carries the real meaning: 4xx says "the caller can fix this", 5xx says "the callee must fix this". Monitoring tools, retry libraries and browser caches all read it exactly that way — which is why returning 200 with {"error": "something failed"} is the single most damaging habit in API design: it makes success indistinguishable from failure to every machine on the path.

2xx — success, precisely

200 OK — the default success. Use for reads and for updates that return the updated resource.

201 Created — the request created a resource. Send a Location header pointing at it:

HTTP/1.1 201 Created
Location: /orders/42

{ "id": "42", "status": "placed" }

202 Accepted — the work started but has not finished: report generation, webhook dispatch, anything queued. Pair it with a status endpoint the client can poll.

204 No Content — success with nothing to say. DELETE responses are the classic case. Careful: a 204 body must be empty; clients that try to parse one get confusing errors.

206 Partial Content — byte ranges for large downloads; mostly the domain of file servers.

What professionals choose: 201 + Location on create (never a bare 200), 204 on delete, and 202 + status URL for anything asynchronous.

3xx — redirects, and the cache question

The pair that matters:

  • 301 / 302: moved permanently / found — clients may change the method to GET on the retry (browsers do this for 301/302 after POST, historically).
  • 307 / 308: temporary / permanent — the method is preserved exactly.

If you move POST /v1/payments to /v2/payments, 308 is correct and 301 would silently turn retries into GETs. For URL shorteners and moved web pages, 301 is what caches honour permanently — test with care, because fixing a wrong 301 means waiting out every cache on the path.

304 Not Modified is how conditional GET works: the client sends If-None-Match: "abc", the server answers 304 with no body, and caches along the way save the bandwidth. Free performance for read-heavy APIs.

4xx — the caller's problem

400 Bad Request — malformed syntax: broken JSON, a query parameter that is not a number. If the request is well-formed but fails your business rules, that is 422.

401 Unauthorized — really "unauthenticated": no credentials, expired token, bad signature. The name is wrong and the industry has agreed to live with it. Always pair with WWW-Authenticate: Bearer.

403 Forbidden — authenticated but not allowed. The token is fine; this account cannot touch this resource. Do not leak existence: for data belonging to someone else, returning 404 instead of 403 is the safer convention (it hides that the resource exists).

404 Not Found — no such resource. Also the honest answer to DELETE /orders/999999 for an order that never existed.

409 Conflict — the request collides with current state: creating an account whose email is taken, cancelling an already-shipped order. The body should explain the conflict.

410 Gone — deliberately removed and not coming back. Useful for retired API versions: /v1/orders answering 410 documents itself.

415 Unsupported Media Type — you sent XML to a JSON endpoint. Pair with an error naming the accepted types.

422 Unprocessable Content — syntactically valid, semantically wrong: negative quantity, unknown SKU, dates in the wrong order. This is where field-level validation errors belong:

HTTP/1.1 422 Unprocessable Content

{ "error": "validation_failed",
  "fields": { "qty": "Must be at least 1", "sku": "Unknown SKU" } }

429 Too Many Requests — rate limiting. Send Retry-After: 30 and, ideally, RateLimit-Remaining/RateLimit-Reset. Clients that respect it are the well-behaved ones; the header is what makes them so.

The 400 vs 422 debate: both appear in production APIs and both are defensible — Rails made 422 the validation default, while many JSON APIs use 400 for everything syntactically fine but semantically wrong. What professionals actually choose: one of the two, consistently, documented — and never 200 with an error body.

5xx — the server's problem

500 Internal Server Error — the unhandled exception. Never include stack traces in the body; log them, return a reference ID.

502 Bad Gateway / 504 Gateway Timeout — the proxy upstream failed or timed out. These are usually your infrastructure talking; a client seeing them should retry with backoff.

503 Service Unavailable — deliberate: maintenance mode or overload. Retry-After: 300 makes it graceful.

Which codes do clients automatically retry? The safe answer: 5xx and 429 (honouring Retry-After), and never 4xx. Idempotent requests (GET, PUT, DELETE) are safe to retry on any timeout; POST needs an idempotency key before a retry loop is safe.

The mistakes that cost real time

Mistake What it breaks Do instead
200 with {"success": false} Monitoring, caches, retry logic Real status code; details in body
401 for permission problems Clients loop refreshing tokens 403 (or 404 to hide existence)
404 for a wrong method Client thinks the resource is gone 405 Method Not Allowed + Allow header
400 for validation misses No field-level detail contract 422 with a fields object
302 on a moved API endpoint POSTs become GETs mid-retry 307/308
500 with a stack trace Information disclosure Reference ID in body, trace in logs

The working set

If you internalise only this much, you will be right in 95% of situations: 200, 201 + Location, 204, 304; 301/308 for moves; 400, 401, 403, 404, 409, 422, 429 + Retry-After; 500, 502, 503 + Retry-After. Everything else is a corner worth knowing exists — the MDN status reference is the honest complete list.

The body is half the contract too: what those codes should contain — bare resources vs envelopes, error shapes, pagination — is the subject of our guide to designing API responses. And when an API hands you JSON that does not parse, our JSON validator pinpoints the offending line and column before you blame the status code.

Tools in this article

  • JSON Formatter: Beautify, minify, validate and sort JSON. Handles escaped/stringified JSON.
  • URL Encoder: Percent-encode and decode URLs; parse URL parts and query parameters.