HTTP status code reference · Guide
Common HTTP Error Codes: 400, 401, 403, 404, 422, 429, 500, 502, 503
Roughly a dozen status codes account for almost every failure you will debug. The hard part is rarely the list — it is the pairs that look interchangeable and are not: 401 against 403, 400 against 422, 502 against 503. Each pair points at a different layer, so getting it right tells you where to look before you open a single log file.
Read the first digit first
The leading digit tells you who is responsible, and that single fact narrows the search more than the code itself. 4xx means the request was wrong — something about the method, path, headers, auth or body did not satisfy the server, and repeating it unchanged will fail again. 5xx means the request was acceptable but the server or something behind it failed, so the same request may well succeed on retry.
That distinction decides where you look. A 4xx sends you to the client: the URL, the token, the payload. A 5xx sends you to the server, its dependencies and its logs. Chasing a 502 through your request-building code is wasted time.
Two classes are easy to forget. 2xx includes more than 200 — 201 Created returns the new resource, 204 No Content deliberately has an empty body, so parsing it as JSON throws. And 3xx is not an error at all: 301 is permanent, 302 and 307 are temporary, and 304 Not Modified is a successful cache validation that many clients mistakenly treat as a failure.
400 Bad Request — the server could not understand you
400 means the request was malformed at a level the server could not get past: broken JSON, a bad query string, a header it could not parse, a body that does not match the declared Content-Type. The server never got as far as your business logic.
The most common cause in practice is a body that is not what the header promises — sending JSON while the client library defaults to application/x-www-form-urlencoded, or forgetting to serialise an object.
curl -i -X POST https://api.example.com/users \
-H 'Content-Type: application/json' \
-d '{"name": "Ada",}'
HTTP/1.1 400 Bad Request
{"error": "Unexpected token } in JSON at position 16"}That trailing comma is invalid JSON, so the request dies before reaching any handler. If a 400 body mentions a parse position, treat it as a syntax problem in what you sent, not as a rejected value.
401 vs 403 — the pair that gets confused most
401 Unauthorized means the server does not know who you are. Credentials were missing, malformed, or expired. The name is a historical misnomer: it is about authentication, not authorization. A correct 401 must carry a WWW-Authenticate header telling the client how to authenticate.
403 Forbidden means the server knows exactly who you are and you still may not do this. The credentials are valid; the permission is not there. Retrying with the same token will never help.
The practical rule: if presenting different credentials could fix it, it is 401. If no credential the caller possesses would fix it, it is 403.
# expired token -> 401, refresh and retry
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token"
# valid token, insufficient scope -> 403, do not retry
HTTP/1.1 403 ForbiddenThis matters beyond tidiness. Clients commonly auto-refresh a token on 401. Returning 401 for a permissions problem sends them into a refresh loop that can never succeed, and each iteration looks like a fresh auth failure in your logs.
404 and 410 — missing now, or gone for good
404 says the server found nothing at this path, without committing to whether it ever existed. It covers both a typo in the URL and a record that was deleted.
410 Gone is the deliberate version: this existed and will not come back. It matters for crawlers — Google retires a 410 faster than a 404, which is why it is the better answer for permanently removed content.
One judgement call comes up constantly in APIs: a resource that exists but belongs to another tenant. Returning 403 confirms the ID is real, which leaks information. Many APIs return 404 instead so that unauthorised callers cannot probe for valid IDs.
422 vs 400 — syntax against meaning
422 Unprocessable Content is for a request the server parsed perfectly and still cannot accept. The JSON is valid, the fields are present, and a value is wrong: an email without an @, a negative quantity, an end date before its start date.
The split is worth keeping. 400 means fix your serialisation; 422 means fix your data. A client can act on that difference — a 422 is worth showing the user field by field, whereas a 400 is a bug in the client.
HTTP/1.1 422 Unprocessable Content
{
"errors": {
"email": ["must contain @"],
"end_date": ["must be after start_date"]
}
}Not everyone follows this. Plenty of well-known APIs return 400 for validation errors, so read the docs rather than assuming. If you are designing the API, pick one convention and hold it — mixed signalling is worse than either choice.
429 Too Many Requests — respect the header
429 means you tripped a rate limit. The important part is not the code but the Retry-After header, which gives either a number of seconds or an HTTP date.
HTTP/1.1 429 Too Many Requests
Retry-After: 30
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1735689600Retrying immediately is the standard mistake — it extends the ban on many gateways. Wait for Retry-After when it is present, and otherwise back off exponentially with jitter so that a fleet of clients does not retry in lockstep.
If you see 429 without ever coming near the documented limit, check whether the limit is per IP rather than per key. Anything behind shared NAT — CI runners, an office network, a serverless region — can exhaust a per-IP budget that no individual caller is responsible for.
500 — the one you should never return on purpose
500 means an unhandled failure: an exception escaped, a null dereferenced, a query blew up. It carries no information for the caller by design, because anything specific risks leaking internals.
Treat every 500 in your own service as a bug, not a status. If a condition is expected — validation, a missing record, a rate limit — it deserves its own code. A 500 that happens routinely is a handler you forgot to write, and it hides real incidents inside noise you have learned to ignore.
When you get a 500 from someone else's API, the response body is often more useful than the code: many services include a request or trace ID. Capture it, because it is usually the only thing their support team can act on.
502, 503, 504 — three different upstream failures
These three come from a proxy, load balancer or gateway rather than from your application, and they say distinctly different things.
502 Bad Gateway — the proxy reached the upstream and got a reply it could not use. In practice the application crashed mid-response, closed the connection, or died on boot. Look at the application log, not the proxy log.
503 Service Unavailable — the server is up but deliberately refusing: maintenance mode, no healthy backends, or a full connection pool. Often accompanied by Retry-After. This is the correct code to serve during a deploy.
504 Gateway Timeout — the upstream was reachable but did not answer in time. Almost always a slow query, a blocking external call, or a proxy timeout set below the real response time.
# nginx: which one you get depends on the failure mode
502 upstream sent no valid response -> app crashed or exited
503 no live upstreams -> all backends failing health checks
504 upstream timed out -> proxy_read_timeout exceededThe 502-after-deploy pattern is worth recognising: the proxy starts routing before the new process finishes booting. A readiness check that gates traffic until the app actually serves requests removes it.
Narrowing it down in one request
Most of the guesswork disappears if you look at the full exchange rather than the code alone. curl -i prints status and headers together, which is usually enough.
curl -i -X POST https://api.example.com/v1/orders \
-H 'Authorization: Bearer $TOKEN' \
-H 'Content-Type: application/json' \
-d '{"sku":"A-1","qty":2}'Then read in this order. Is it 4xx or 5xx — client or server? On a 4xx, is there a WWW-Authenticate header (auth) or a field-level error body (validation)? On a 5xx, is there a Retry-After or a Server header naming a proxy — which separates a deliberate 503 from a crash?
Two checks catch a surprising share of confusing cases. Add -L to follow redirects: a 405 or 404 on a POST often turns out to be a 301 that silently converted the method to GET. And compare against a request you know works — if both fail identically, the problem is the environment, not the endpoint.
Frequently asked questions
What is the difference between 401 and 403?
401 means the server cannot identify you — credentials are missing, malformed or expired, and different credentials could fix it. 403 means you were identified and still lack permission, so retrying with the same token will never work. A correct 401 includes a WWW-Authenticate header.
Should validation errors return 400 or 422?
422 is the more precise answer: the request parsed correctly and a value was unacceptable. 400 is for input the server could not parse at all, such as malformed JSON. Many APIs use 400 for both, so check the documentation before assuming — and if you are designing one, apply whichever rule you choose consistently.
What causes a 502 Bad Gateway?
A proxy reached your application and received a response it could not use — usually because the process crashed, exited mid-response or has not finished starting. Check the application log rather than the proxy log. After a deploy it normally means traffic was routed before the new process was ready.
Why do I get 429 when I am under the rate limit?
The limit is often applied per IP rather than per API key. Anything behind shared NAT — CI runners, an office network, a serverless region — can exhaust a per-IP budget even when no single caller is close to the documented limit. Honour Retry-After and back off with jitter instead of retrying immediately.
Is 404 or 403 correct for a resource belonging to another user?
Both are defensible. 403 is literally accurate but confirms the ID exists, which lets an unauthorised caller enumerate valid IDs. Many APIs return 404 for anything the caller may not see, so that unauthorised and non-existent are indistinguishable from outside.
Is 304 Not Modified an error?
No. It is a successful cache validation telling the client its stored copy is still current, and it has no body by design. Client code that treats any non-200 as a failure will misreport it, which is a common source of phantom errors in monitoring.
Ready to try it?
Open the free browser-based HTTP status code reference and apply what you just read — no sign-up, runs locally.
Open the HTTP status code reference tool