Choosing the right 4xx
400 or 422, 404 or 405, 409 or 412, and the order an API checks a request in — which decides which error a broken request actually gets.
After this lesson you can
- Pick between 400, 415 and 422 for a request the server cannot use
- Explain what a 405 must carry and how it differs from 404
- Order the checks on a request so the error a client sees is the most useful one
- Use 404 to avoid revealing that a forbidden resource exists
A 4xx puts the problem on the client's side, and each code says what the problem is — a client can only fix what it is told about. These are the codes an API uses most, with the distinctions interviewers like to probe.
| Code | Use it when |
|---|---|
| 400 Bad Request | The request is malformed: unparseable JSON, a broken header, a query parameter of the wrong shape |
| 401 Unauthorized | No valid credentials. Must carry WWW-Authenticate |
| 403 Forbidden | Understood and refused — typically valid credentials without permission |
| 404 Not Found | No current representation at this URI — or one you choose not to reveal |
| 405 Method Not Allowed | The resource exists but not this method. Must carry Allow |
| 406 Not Acceptable | No representation matches the request's Accept header |
| 409 Conflict | The request conflicts with the resource's current state |
| 410 Gone | It existed, it is gone for good, and that is known |
| 412 Precondition Failed | An If-Match-style condition was false |
| 415 Unsupported Media Type | The body is in a format this endpoint does not accept |
| 422 Unprocessable Content | The body parsed fine but its content is invalid |
| 429 Too Many Requests | Rate limited (RFC 6585) |
400, 415 or 422
Three different failures of the same POST:
Content-Type: text/xmlon a JSON-only endpoint is 415: the server will not even try to read it.{"quantity": 3,(cut off) is 400: it claims to be JSON and is not.{"quantity": -3}is 422: perfectly good JSON, meaningless as an order. RFC 9110 defines 422 for exactly this case: a content type the server supports and correct syntax, but instructions it cannot process.
Some APIs use 400 for validation errors too, and that is defensible as long as it is consistent. What is not defensible is mixing them by accident from one endpoint to the next.
404 or 405, and 409 or 412
404 is about the URI; 405 is about the method on a URI that exists, and
RFC 9110 requires the 405 to list what is allowed in an Allow header.
409 means the request clashes with the resource's state — a duplicate
username, cancelling an order that has shipped. 412 is narrower: the
client sent a precondition (If-Match) and it was false, usually because
someone else changed the resource first.
Hiding what exists
A 403 confirms the resource is there. For /accounts/981 that is
already a leak — an attacker can enumerate ids and learn which exist.
RFC 9110 explicitly allows an origin server to answer 404 instead of
403 to hide a forbidden resource's existence. The order of checks
decides whether that works:
const accounts = { "981": { owner: "ana" }, "982": { owner: "nino" } }; // Look the resource up, then ask whether the caller may see it.function existsThenAuthorize(id, caller) { const acc = accounts[id]; if (!acc) return 404; if (acc.owner !== caller) return 403; return 200;} // Treat "not yours" exactly like "not there".function hideForbidden(id, caller) { const acc = accounts[id]; if (!acc || acc.owner !== caller) return 404; return 200;} export function run() { const ids = ["981", "982", "983"]; return { existsThenAuthorize: ids.map((id) => `${id}:${existsThenAuthorize(id, "ana")}`), hideForbidden: ids.map((id) => `${id}:${hideForbidden(id, "ana")}`), };}Order of checks
A request can be wrong in several ways at once, and it gets one status code, so an API needs a fixed order. A sensible one goes from the outside in: does the route exist (404), is the method allowed (405), who is calling (401), can the body be read (415, then 400), is it valid (422), does this caller get this resource (404 or 403), and does it fit the current state (409). Validating before authenticating, for example, hands detailed field errors to anonymous callers.
Common mistakes
- 404 for "this route does not support DELETE": the client concludes the
resource is gone. That is 405 with an
Allowheader. - A 405 without
Allow— RFC 9110 requires it. - 403 for a resource the caller is not allowed to know about, which confirms its existence; 404 hides it.
- 400 for a well-formed body with an invalid value on one endpoint and 422 on the next. Pick one convention for the whole API.
What interviewers ask
- 400 or 422 for a negative quantity? — 422 means syntactically valid but semantically wrong; 400 is malformed. Either can be defended if used consistently; mixing them cannot.
- A user requests another user's private order. 403 or 404? — 404 if the existence itself is private, which RFC 9110 permits; 403 if the caller may know it exists.
- A request has bad JSON and no auth token. Which error wins? — 401; identify the caller before reading the body, so anonymous callers learn nothing about it.
Exercise · Pro
One status for a broken request
Exercises are free in the first 3 lessons of every course. From here on they are part of Pro and Sprint; the lesson and its runnable examples stay open.
Upgrade