HTTP Status Codes Are Being Used Wrong and It Is Your Problem Too
The HTTP status code specification is 30 years old, fully documented, and widely misimplemented. The misimplementation is not ignorance — most API developers know that 200 means success and 404 means not found. It is the edge cases where the correct status code requires a moment of thought that the wrong choice gets made, and the wrong choice gets propagated to every consumer who must now handle an error that does not mean what the specification says it means.
The consequences are practical. A consumer that implements error handling based on the HTTP specification — treating 4xx as client errors requiring consumer-side fixes and 5xx as server errors warranting retry — will behave incorrectly when the API uses status codes inconsistently. The specification’s value as a shared vocabulary for error semantics depends on implementations that honor it.
The 200 for Everything Anti-Pattern
The most common status code misuse is returning 200 OK for responses that are not successful. APIs that return a 200 with a body containing {"success": false, "error": "User not found"} are forcing consumers to parse the response body to determine success or failure rather than using the HTTP status code that was designed for exactly this purpose. Monitoring infrastructure, API gateways, and log analysis tools that aggregate error rates based on HTTP status codes will classify these failures as successful requests.
The motivation for this pattern is usually application layer simplicity — the API always returns 200, and error discrimination is done in the response body. The cost is every consumer that must implement body-parsing error detection in addition to status code checking, and every infrastructure tool that cannot surface failure rates correctly.
The 4xx vs 5xx Distinction
The distinction between 4xx client errors and 5xx server errors has clear semantics that are frequently collapsed. A 500 Internal Server Error signals that the server encountered an unexpected condition that prevented it from fulfilling the request — a bug, an unhandled exception, a downstream dependency failure. A consumer that receives a 500 knows the problem is on the server side and that retrying the same request may eventually succeed when the server condition resolves.
A 400 Bad Request signals that the consumer sent a request that the server cannot process because of a client-side error — invalid parameters, missing required fields, malformed request body. A consumer that receives a 400 knows that retrying the same request will produce the same result. The request must be changed before it will succeed.
APIs that return 500 for input validation errors — treating “the user sent invalid data” as an unexpected server condition — will confuse consumers into retrying requests that should be fixed instead. APIs that return 400 for server-side failures — treating “the database is unavailable” as a client error — will cause consumers to give up on requests that would succeed if retried.
The Specific Status Codes Worth Getting Right
401 Unauthorized means the request requires authentication that was not provided or was invalid. 403 Forbidden means the authenticated caller does not have permission for the requested resource. These are different errors with different consumer responses — a 401 prompts re-authentication, a 403 prompts access control review. APIs that return 403 for unauthenticated requests, or 401 for authenticated requests that lack permission, produce consumers that attempt the wrong remediation.
404 Not Found is overloaded in many APIs to mean both “this endpoint does not exist” and “this resource instance does not exist.” These are meaningfully different errors. An endpoint that does not exist is likely a client programming error — the consumer has the wrong URL. A resource that does not exist may be a legitimate application state — the order was not found, the user has been deleted. Both are correctly 404. The distinction matters for error messages and monitoring, not for the status code.
422 Unprocessable Entity is the correct status code for requests that are syntactically valid but semantically invalid — a well-formed JSON body that fails business rule validation. The distinction between 400 (malformed request) and 422 (valid request, invalid business logic) is meaningful for consumer error handling and is worth the two extra keystrokes it requires to get right.
The Retry-After Header for 429 and 503
Both 429 Too Many Requests and 503 Service Unavailable should be accompanied by a Retry-After header indicating when the consumer should retry. Without it, consumers implement exponential backoff against an unknown recovery time. With it, consumers implement intelligent retry that matches actual server recovery expectations. The header costs nothing to add. The consumer behavior improvement is significant.
Status codes are a communication protocol between API provider and consumer. Using them incorrectly is not a harmless shortcut. It is a contract violation that propagates incorrect behavior to every consumer who implements the specification correctly.