HTTP Status Codes: A Debugging Guide for API Integrations
A status code is the server's one-line diagnosis of your request. Read it precisely and you usually know whose bug it is — yours, theirs, or the network's — before opening a single log. Here's what each family really tells you, including the pairs that developers mix up daily.
The pairs everyone confuses
401 vs 403 — "who are you?" vs "you can't do that"
- 401 Unauthorized: the server doesn't know who you are — missing, expired, malformed or badly signed credentials. Fix the authentication: is the header present, is the token fresh, is the word
Bearerthere? (Scheme-by-scheme pitfalls: API authentication guide.) - 403 Forbidden: it knows exactly who you are — and the answer is no. Fix the authorization: role, scope, plan, or you're touching someone else's resource.
400 vs 422 — "can't parse it" vs "parsed it, it's wrong"
- 400 Bad Request: the request is malformed — broken JSON, invalid encoding, missing Content-Type. Check syntax first: a trailing comma, an unquoted key, a body sent as
text/plain. - 422 Unprocessable Entity: syntactically fine, semantically rejected — a validation failure like a negative price. The response body usually names the field; read it before re-reading your code. (Many APIs use 400 for both — the response body is the tiebreaker.)
404 — not always "wrong URL"
Besides a typo'd path, a 404 can mean an unresolved variable ({{baseUrl}} empty → request went to the wrong host), a missing API version prefix (/v1/), or a deliberate mask for 403 so the API doesn't reveal that the resource exists.
The rest of the 4xx family worth knowing
| Code | Meaning | First thing to check |
|---|---|---|
| 405 | Method not allowed | POST vs PUT vs PATCH — check the docs, and the Allow response header |
| 409 | Conflict | Duplicate unique field, or stale version in optimistic locking |
| 415 | Unsupported media type | Your Content-Type header |
| 429 | Too many requests | Retry-After header; add backoff, don't hammer |
5xx — probably their bug, but verify
- 500 Internal Server Error: the server crashed handling your request. It's their defect even if your input triggered it (a robust API validates and returns 4xx) — but do minimize the failing request: remove fields until it stops crashing, and report the one that kills it.
- 502 / 504 Bad Gateway / Gateway Timeout: a proxy or load balancer couldn't reach the upstream. Usually transient or a deploy in progress; retry with backoff.
- 503 Service Unavailable: overload or maintenance; honor
Retry-After.
No status at all? It never reached the server
Connection refused, DNS failure, TLS certificate errors and timeouts are transport failures — there is no HTTP response to read. Triage differently: is the host reachable, is the port right, is a VPN or proxy in the way, did you hit http:// on an HTTPS-only port? RestRuno explains these cases in plain language in the response panel ("connection refused by localhost:9999", host not resolving, TLS problem) with a What to check list.
A 60-second triage flow
- Read the code family. 4xx → inspect your request. 5xx → minimize and report. No response → transport.
- Read the response body. Good APIs tell you the exact field and reason.
- Inspect the final request — after variable resolution. Half of "the API is broken" is an empty
{{token}}. - Reproduce it minimally. Strip the request to the smallest failing version; the removed piece is your answer.
- Pin it with a test so the regression can't return:
rr.expect(rr.response.status).toBe(200)is the cheapest alarm system you'll ever install. (More in API testing best practices.)
Debug faster with RestRuno
Plain-language network error explanations, a console that logs every request, and tests that pin the fix. Free for Windows, macOS and Linux.
Download RestRuno — free