Status Code
Concept
A three-digit number reporting how a request was handled, which is what the caller uses to decide what to do next.
Why it exists
Whoever sent the request needs the outcome before choosing a next move: refresh the screen on success, redirect to sign-in when the session expired, retry shortly if the server tripped over momentarily. Three different situations, three different next actions. Making that decision from the words in the response body means the caller has to parse prose.
Pagedeck's first API worked that way. Every response came back as a success and the outcome was described inside the body. The app had to open the body and read a result field, and one change of wording stopped error screens appearing at all. The larger problem was that the error rate read zero. Every response was recorded as a success, so the monitoring dashboard stayed calm throughout a 30-minute outage.
Hence the agreement to put the outcome in a number, in a place separate from the body. Programs can classify a response without opening it, and so can the infrastructure sitting in between.
How it works
It's three digits, and the first one sets the class. Callers generally branch on the first digit for the broad decision and on all three for the specifics.
- 1xx means still in progress, and rarely needs handling directly.
- 2xx means success.
- 3xx means the thing lives at a different address.
- 4xx means the problem is on the calling side, whichever HTTP method was used.
- 5xx means the problem is on the receiving side, the server, and the caller did nothing wrong.
Fewer than a dozen specific values cover most work.
- 200 — handled, and the result is in the body.
- 201 — something new was created. The natural response to a request that creates a document.
- 204 — handled, and there's nothing to return. Common for deletions, and worth using deliberately rather than returning an empty object with a 200.
- 301 and 302 — the address moved, permanently and temporarily in that order.
- 400 — the shape of the request is wrong: a required field missing, letters where a number belongs.
- 401 means the caller hasn't been identified; 403 means they have been, and aren't allowed to do this. Not signed in gets 401; a signed-in member opening an admin screen gets 403. Mixing these two is the most frequent mistake in this whole list.
- 404 — there's no resource at that address.
- 409 — the request conflicts with the current state, like creating something under a name already taken.
- 429 — called too often, usually accompanied by advice on when to try again.
- 500 — an unexpected error inside the server; 503 — can't accept this right now, come back later.
What the number decides is the caller's next action. On 401, send the user to sign-in; on 429 and 503, wait and send it again; on 400, sending it again produces the same result, so tell the user what was wrong. That 5xx is worth retrying and 4xx needs the request changed is the basis of every retry rule.
Explanations for humans belong in the response body, the payload. The number is a conclusion for machines and the body is a reason for people, and both have to travel together for the app to branch and the user to understand.
Where it shows up
The same number is read by people, by programs and by infrastructure. Four places matter most.
- API documentation. Which value each endpoint returns in which case takes up a section of it. If one endpoint returns 404 for a missing resource while another returns 200 with an empty list, the app needs different code per endpoint, and every new screen inherits that inconsistency.
- Monitoring and alerting. Rules get written as "page the on-call engineer when 5xx exceeds one percent." Misuse the number and that alert never fires, which is a worse outcome than having no alert at all, because the dashboard actively reassures you.
- Error handling in the interface. Redirect quietly on 401, say permission is missing on 403, suggest trying again shortly on 500. One shared message for all three leaves users with nothing to act on.
- Search engines and intermediaries. Return 200 for a page that no longer exists and search engines keep it in the index; return 301 and the old address passes its standing to the new one. Caches also read this number to decide whether to store a response, which is how one mislabelled error reaches users who never triggered it.
How it sounds at work
"This needs to be a 401. It's a 403 right now, so the app can't route to sign-in."
The two mean different things and the caller's handling diverges.
"Errors are being wrapped in 200s, so the 5xx alert never fires."
The outcome isn't in the number, so the monitoring doesn't work.
"Retry on 429, don't retry on 400."
Retry policy is written against classes of this number rather than against individual endpoints.
When Theo reorganised the JSON response format, he fixed two rules: the outcome goes in the number and the reason goes in the body, and the same meaning always gets the same number. Going through thirty endpoints, seven that returned 200 for a missing resource became 404, and four that threw 500 on validation failures came down to 400, which also took them out of the outage alert. The following week the error rate showed a non-zero value for the first time — and revealed that it had been at that level for two months.
What people get wrong
The most common position is that returning success for everything and describing errors in the body is easier to work with. Caller code collapses into one branch, so it looks that way. That choice gives up three things at once: the error rate goes to zero and the alerting dies, caches store error responses as valid and serve them to other users, and there's no basis for separating what's worth retrying from what isn't. The body carries the reason; the number carries the conclusion, and only one of the two is readable by anything other than your own client code.
The other habit is reading 4xx as "not our fault." The class says the problem is on the calling side, which sounds like nothing to do on the server. In practice, a pile of 400s on one endpoint is a signal about us: documentation that doesn't match the behaviour, a body that never says which field was wrong so the same mistake repeats, or a rule that's difficult to comply with. One Pagedeck endpoint sat at 12 percent 400s because it had accepted two date formats, then narrowed to one, and nobody updated the documentation.
Where to go next
- API
the specification this value is one part of
- HTTP method
its counterpart on the request side
Last updated 2026-09-16