HTTP 400 vs 422 — Bad Request vs Unprocessable Entity Comparison
http 400 vs 422 explained: when to return Bad Request for malformed syntax versus Unprocessable Entity for valid syntax that fails business validation. Includes API examples.
Quick Cheat Sheet
| Layer | 400 Bad Request | 422 Unprocessable Entity |
|---|---|---|
| Failure type | Syntax / parsing | Semantics / validation |
| Can the server parse it? | No | Yes |
| Originally specced for | Generic HTTP | WebDAV (RFC 4918), now broadly used |
| Typical body | Error message | Field-level validation errors |
What Each Code Actually Means
400 Bad Request (RFC 9110 § 15.5.1) is the "I literally cannot make sense of this" response. Malformed JSON, missing required headers, invalid query-string encoding, oversized cookies — anything where the request itself is structurally broken.
422 Unprocessable Entity (originally RFC 4918, formalized in RFC 9110 § 15.5.21) is for requests that are well-formed but contain instructions the server cannot execute. The classic example: a JSON body that parses fine but has age: -5 in a field that requires a positive integer.
Where the Line Lives
If your framework can deserialize the body without throwing, you've passed 400 territory. Anything beyond that — required-field checks, format constraints (email, UUID), range checks, cross-field rules, business rules — should produce 422.
Real-World API Examples
- GitHub API is the canonical example: it returns 422 with a structured
errorsarray when you POST a valid JSON body that fails validation (e.g., creating a repo with a name that already exists). - Stripe API mostly skips 422 and uses 400 with a typed error object (
type: "invalid_request_error"), which is a deliberate design choice for backward compatibility. - Rails with
render json: ..., status: :unprocessable_entityis so common that 422 is sometimes called "the Rails error code."
When to Prefer Which
- New API? Use 422 for validation, 400 for parse failures. It's clearer and aligns with most modern style guides (JSON:API, IETF problem-details).
- Talking to legacy clients that don't understand 422? Use 400 consistently and put the error category in the body.
Never return 200 with an error field in the body — that's the worst of both worlds.
Real-World Use Case
A typical FastAPI or Express endpoint should return 400 when JSON.parse throws on the request body, but 422 when Zod/Pydantic schema validation fails. For a /users endpoint, missing closing brace = 400, while {"email": "not-an-email"} = 422 with a per-field error map.
Look Up Any Status Code
Related Comparisons
HTTP 401 vs 403 — Unauthorized vs Forbidden Status Code Comparison
Confused about http 401 vs 403? Learn the exact difference between Unauthorized and Forbidden, when each is returned, and how Express, Stripe, and GitHub APIs use them.
HTTP 409 vs 412 — Conflict vs Precondition Failed Comparison
http 409 vs 412: both signal state conflicts, but Conflict is for resource-level clashes while Precondition Failed is triggered by If-Match / If-None-Match headers. ETag examples included.
HTTP 415 vs 406 — Unsupported Media Type vs Not Acceptable Comparison
http 415 vs 406: 415 is about Content-Type the client sent, 406 is about Accept the client wants. Learn the request vs response negotiation difference.
HTTP 414 vs 413 — URI Too Long vs Payload Too Large Comparison
http 414 vs 413: 414 fires when the URL itself is too long, 413 when the request body is too big. Learn typical limits in Nginx, ALB, and Cloudflare.