Choosing Status Codes
A handful of status codes cover almost every case. The skill is in the close calls — the pairs that are easy to confuse.
The ones you actually use
Section titled “The ones you actually use”200 OK— success with a body.201 Created— a resource was created; include aLocationheader.204 No Content— success, nothing to return (e.g. aDELETE).400 Bad Request— the request is malformed (bad JSON, missing required parts).401 Unauthorized— no/invalid credentials; the client must authenticate.403 Forbidden— authenticated, but not allowed.404 Not Found— no such resource.409 Conflict— the request conflicts with current state (duplicate, version clash).422 Unprocessable Entity— syntactically valid but semantically invalid (failed validation).429 Too Many Requests— rate limited.500 Internal Server Error/503 Service Unavailable— the server failed.
The close calls
Section titled “The close calls”- 200 vs 201 vs 204 — returning a body →
200; created a resource →201; nothing to say →204. - 400 vs 422 — can the server even parse the request? Unparseable →
400; parsed fine but the values are invalid →422. - 401 vs 403 — do we know who you are? Not authenticated →
401; authenticated but not permitted →403. - 404 vs 409 — the resource does not exist →
404; it exists but your request conflicts with its state →409.
Codes in a running API
Section titled “Codes in a running API”Common mistakes
Section titled “Common mistakes”Returning 200 with an error in the body Symptom:
HTTP 200 OKwith{ "success": false, "error": "Not found" }- Monitoring that watches HTTP status thinks the request succeeded
- Alerts and SLA measurements go wrong
- Use the right status code:
404, not200
Using 500 for a client error Symptom:
- Validation fails → the server throws → returns
500 - The client can’t tell whether its own request was wrong or the server broke
- Catch validation errors first → return
400or422
💡 Real-world examples
GitHub API:
422 Unprocessable Entityfor validation errors — a field breaks a rule409 Conflictfor a duplicate resource403 Forbiddenfor an action you don’t have permission for (as opposed to401, not authenticated at all)Stripe API:
- A full range of status codes:
400invalid request,401auth failed,402payment failed,429rate limited- The HTTP status always maps to the error type
Sources: GitHub REST API errors, Stripe API — errors