🌳 API

HTTP Status Code Picker

Not sure which HTTP status code to return? Answer a few questions and this interactive decision tree will find the right one for your API response.

📖 How to Use
1
Read the question and click the answer that matches your scenario
2
Follow the decision path — each answer narrows down the correct status code
3
Get the recommended code with description and when to use it

When a 200 Still Means Something Went Wrong

A recurring anti-pattern in API design is an endpoint that returns 200 OK for every request it successfully processed, regardless of whether the underlying operation actually succeeded — the real outcome gets encoded instead in a JSON field inside the body, something like {"status": "declined"}. GraphQL-over-HTTP is a well-known real-world example of this by design: nearly every GraphQL response returns 200 even when the query itself failed, which is exactly why GraphQL gateways and monitoring tooling had to be built to parse response bodies rather than trust transport status. A client that always reads the full body handles this fine. Anything downstream that inspects only the HTTP status code — a load balancer's health check, a CDN's cache-control logic, an uptime monitor, a webhook consumer routing on status — cannot see the failure at all, because as far as the transport layer is concerned, the request succeeded.

The problem generalizes beyond any single field: HTTP status codes exist precisely so that generic infrastructure between a server and its clients can make correct decisions without understanding the application's business logic. The moment a business-level failure — a declined charge, a rejected validation, a failed downstream dependency — is wrapped in a 200 response, every HTTP-aware layer in that path loses the ability to react to it: it won't retry, won't alert, and won't distinguish "worked" from "didn't." Reserving 2xx exclusively for genuine success and routing everything else through the matching 4xx or 5xx class keeps that signal intact for every intermediary that depends on it, which is the whole reason status codes exist as a channel separate from the response body in the first place.

The RFCs That Define These Numbers

HTTP status codes are standardized in the IETF's HTTP semantics specification — originally RFC 2616, later split and refined into RFC 7231, and consolidated again into RFC 9110 — which defines the five numeric classes (1xx through 5xx) and the specific meaning of each registered code. Some codes used constantly in modern APIs were added well after the original HTTP/1.1 spec: 429 Too Many Requests arrived in RFC 6585 in 2012 to standardize rate-limit signaling, and 422 Unprocessable Content originated in WebDAV's RFC 4918 before being adopted broadly by REST APIs for semantic validation failures. The IANA maintains the canonical registry of all assigned status codes, and well-behaved HTTP clients, proxies, and caches are expected to fall back to the general behavior of a code's class (2xx succeeded, 4xx client's fault, 5xx server's fault) even for codes they don't specifically recognize — which is exactly why picking a code from the correct class matters more than memorizing every registered number.

Slip-Ups That Keep Showing Up in Code Review

Picking a Code: A Concrete Walkthrough

Say you're building a DELETE /orders/{id} endpoint and need to decide what to return in three scenarios. If the order exists and is deleted successfully, the tree's "success" branch asks whether there's anything to return — since a DELETE typically has no body, the answer lands on 204 No Content, not 200 OK. If the order id doesn't exist at all, the tree's "client error" branch asks what's wrong with the request, and "resource doesn't exist" resolves to 404 Not Found. Now suppose the order exists but is already in a "shipped" state that your business rules forbid deleting — this isn't a missing resource and it isn't malformed input, so neither 404 nor 400 fits; walking the "client error" branch to "validation failed, correct syntax, wrong values" lands on 422 Unprocessable Content, with a response body explaining that shipped orders can't be deleted. Three requests to the same endpoint shape, three different correct codes, decided entirely by what actually happened server-side rather than by habit.

Moments That Call for the Status Code Tree

Frequently Asked Questions

What is the difference between HTTP 401 and 403?

401 Unauthorized signals that the request lacks valid authentication credentials — the server doesn't know who the caller is. The response should include a WWW-Authenticate header telling the client how to authenticate. 403 Forbidden signals that the server knows who the caller is but refuses to grant access — the resource exists but the caller doesn't have permission to see it. A common security pattern is to return 404 Not Found instead of 403 when you want to conceal the existence of a resource from unauthorized callers.

When should I use 422 instead of 400?

Use 400 Bad Request when the request itself is malformed at the protocol or format level — the JSON body is unparseable, a required header is absent, or the content type is wrong. Use 422 Unprocessable Content when the request is syntactically correct but fails business logic validation — the JSON parses fine but a field value violates a constraint, such as an end date earlier than a start date, a price below zero, or a username that's already taken. This distinction helps API clients distinguish between "fix your serialisation code" (400) and "fix your input data" (422).

What is the difference between 301 and 302 redirects?

301 Moved Permanently tells the client (and search engines) that the resource has permanently moved to the new URL — the client should update any bookmarks and search engines should transfer ranking signals to the new URL. 302 Found (also called "Moved Temporarily") tells the client the resource is temporarily at a different URL but will return to the original — the client should keep using the original URL for future requests. Use 308 Permanent Redirect and 307 Temporary Redirect when you also need to preserve the original HTTP method (POST stays POST) through the redirect.