HTTP status codes explained: when to use 401 vs 403 vs 422
The most important HTTP status codes explained clearly — including rules of thumb for 401, 403 and 422.
HTTP status codes are the shortest form of API documentation — when used correctly. In practice you still see 200 for errors, 500 for validation issues, and 403 for unauthenticated users. Here's the clean version.
2xx – Success
- 200 OK – Standard success response, usually with a body.
- 201 Created – A new resource was created. The
Locationheader should point to it. - 204 No Content – Success, but intentionally no body. Ideal for
DELETEorPUTwhen the client already knows the resulting state.
3xx – Redirects
The difference between 301 and 302 is one of the most common SEO traps.
- 301 Moved Permanently – Permanent. Browsers and search engines cache it aggressively.
- 302 Found – Temporary. Per spec the method may change (POST → GET), and browsers do.
- 307 Temporary Redirect – Temporary, but method is preserved. Safer than 302 for POST.
- 308 Permanent Redirect – Like 301, but method preserved. The modern choice.
4xx – Client errors
This is where most of the confusion lives. 400 is generic (malformed request, e.g. broken JSON), 404means "resource doesn't exist or you may not see it". The interesting trio is 401, 403, and 422:
| Code | Meaning | When to use | Example |
|---|---|---|---|
| 401 | Unauthorized – not authenticated | Missing token, expired token, wrong credentials | API call without an Authorization header |
| 403 | Forbidden – authenticated, but not allowed | User is logged in but lacks permission | Regular user hits an admin endpoint |
| 422 | Unprocessable Entity – validation failed | Request format is valid, but content is logically wrong | Email syntax is fine but already taken |
Other relevant 4xx codes:
- 409 Conflict – State conflict, e.g. concurrent edit or duplicate username.
- 429 Too Many Requests – Rate limit hit. Send a
Retry-Afterheader.
5xx – Server errors
- 500 Internal Server Error – Catch-all for unexpected server problems. Log everything that lands here.
- 502 Bad Gateway – Your reverse proxy (nginx, Cloudflare) got a broken or no response from the backend.
- 503 Service Unavailable – Server is intentionally offline (maintenance, overload). Combine with
Retry-After. - 504 Gateway Timeout – Backend is too slow to respond.
Rules of thumb
- Never 200 for errors — not even with
{ "error": "..." }in the body. Clients and monitoring rely on the status code. - 401 vs 403:the question is not "may you do this?" but "who are you?". Don't know you (or invalid token)? → 401. Know who you are, but you can't? → 403.
- 404 instead of 403 for private content — that way you don't even leak the resource's existence. GitHub does this for private repos.
- 422 instead of 400 for semantic validation errors (missing required field, wrong format). Use 400 for structurally broken requests (invalid JSON).
- 201 with a
Locationheader when you create something. It's REST-standard and helps clients and tooling. - Use 204 when the body is empty — semantically clearer than 200 with an empty body.
Consistency beats perfection: if your team agrees on a clear rule and sticks to it, that's worth more than the theoretically "perfect" choice in every individual case.