Status Codes
The full taxonomy and when to pick each code.
Status Codes
A status code is a three-digit number in the response that tells the client what happened. The first digit is the class. The next two narrow it down. Most production APIs use fewer than fifteen codes, but knowing the full taxonomy prevents the common mistake of reaching for 200 when the operation failed.
Analogy
HTTP status codes are like a pharmacy's counter slips. The first digit is the window the clerk pushes the slip out of: the "pickup" window (2xx, your prescription is ready), the "go to that other counter" window (3xx, they've moved you), the "your form is wrong" window (4xx, fix your paperwork), or the "our computer's down" window (5xx, come back later). The last two digits are which exact stamped explanation is on the slip — 404 is "we've checked the shelves, no such prescription exists for you", 401 is "we need to see ID before we can even look", and 403 is "we saw the ID and we're still not handing this over". Returning 200 with "failed" scribbled in the body is like handing someone a "ready for pickup" slip attached to an empty bag — the counter's filing system no longer matches reality and every downstream system is confused.
The five classes
| Range | Class | What it means |
|---|---|---|
| 1xx | Informational | Request received, still processing |
| 2xx | Success | The request was received, understood, and accepted |
| 3xx | Redirection | Further action needed to complete the request |
| 4xx | Client error | The request is wrong — the client should not retry unchanged |
| 5xx | Server error | The request was valid, but the server failed |
2xx — success
200 OK — The general success code. Use it for GET, PUT, and PATCH responses that include a body.
201 Created — A new resource was created. Always include a Location header pointing to the new resource. Use this for successful POST requests.
202 Accepted — The request was received but processing has not completed. Use for async operations: the server has queued the task and will process it later.
204 No Content — Success, and there is nothing to return. Use for DELETE, or for PUT/PATCH when you do not want to return the updated resource. Never include a body with 204.
3xx — redirection
301 Moved Permanently — The resource has moved forever. Clients and search engines should update their bookmarks. The browser follows the Location header.
302 Found — Temporary redirect. Follow the Location header, but keep using the original URL for future requests.
304 Not Modified — The client's cached copy is still valid. The server sends no body, just the headers. The browser uses its cached response. This is the mechanism behind conditional GET with If-None-Match / ETag.
4xx — client errors
400 Bad Request — The request is malformed: bad syntax, missing required fields, invalid values. Tell the client what is wrong so they can fix it.
401 Unauthorized — Authentication is required and has not been provided or has failed. Despite the name, this is an authentication error, not authorization. The response should include a WWW-Authenticate header.
403 Forbidden — The client is authenticated but not permitted to access this resource. Do not confuse with 401: 401 means "I don't know who you are," 403 means "I know who you are, but no."
404 Not Found — The resource does not exist at this URL. Can also be used deliberately to hide the existence of a resource from unauthorized clients (instead of 403).
409 Conflict — The request conflicts with the current state of the resource. Common for concurrent writes or unique-constraint violations.
410 Gone — Like 404, but permanent. The resource existed and has been deliberately removed. Useful for tombstoning deleted resources so clients know not to keep retrying.
422 Unprocessable Entity — The request is syntactically valid JSON/XML, but the content fails validation. Use this when the structure is fine but the semantics are wrong (e.g., a future expiresAt date supplied as a string passes JSON parse but fails business logic).
429 Too Many Requests — Rate limit exceeded. Include Retry-After in the response so the client knows when to try again.
5xx — server errors
500 Internal Server Error — Something unexpected went wrong. Use this as a catch-all for unhandled exceptions. Do not leak stack traces to clients.
502 Bad Gateway — The server was acting as a gateway/proxy and received an invalid response from an upstream server. Common when a backend service is down.
503 Service Unavailable — The server is temporarily unable to handle the request — typically overload or scheduled maintenance. Include Retry-After.
The 422 vs 400 debate
This is a genuine ambiguity in the spec. Two camps:
Use 400 for everything invalid: simpler, one code to handle, widely understood.
Use 422 for semantic validation errors: more precise. The request was well-formed JSON but the data does not make sense — a past startDate, a negative quantity, a non-existent userId. Reserve 400 for outright malformed requests.
Most modern APIs (Stripe, GitHub) use 422 for validation errors and 400 for truly malformed requests. Pick a convention and apply it consistently across your entire API surface.
problem+json — RFC 7807
RFC 7807 defines a standard error body format so clients can parse errors programmatically rather than screen-scraping your message strings.
{
"type": "https://api.example.com/errors/validation",
"title": "Validation failed",
"status": 422,
"detail": "The field 'email' must be a valid email address.",
"instance": "/users/create/req-abc123",
"errors": [
{ "field": "email", "message": "Invalid email format." },
{ "field": "dob", "message": "Date of birth cannot be in the future." }
]
}
The type is a URI — it does not need to resolve, but it should be stable so clients can use it in switch statements. The instance is the specific request that failed, useful for support tickets.
Use Content-Type: application/problem+json when returning RFC 7807 bodies.
Codes to avoid
418 I'm a Teapot — From RFC 2324 (a 1998 April Fool's joke). Real systems sometimes return this for "this endpoint intentionally refuses the request," which is creative but confusing. Use 400 or 405.
200 with an error in the body — The pattern of returning { "success": false, "error": "not found" } with a 200 status is an antipattern. It breaks HTTP caches, monitoring systems, and any middleware that inspects status codes. Use the right code.
204 with a body — The spec says 204 has no body. Some clients will discard it. Use 200 if you need to return content.