When to Use 422 vs 400 (and Other HTTP Status Code Debates)
400 means the request is malformed. 422 means it's valid but semantically wrong. Here's the practical guide to picking the right 4xx for your API responses.
The 4xx range has 29 defined status codes. Most APIs use about five of them, and those five are frequently misused. The debate between 400 and 422 comes up in every API design review, and frameworks actively disagree with each other on the right answer.
Here’s the practical guide — what each code actually means, when to use it, and what the frameworks default to.
The HTTP Status Codes reference tool on this site has the full RFC definitions for all 4xx codes. Use it alongside this post.
400 Bad Request
The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).
— RFC 9110
400 means: I couldn’t understand the request. The canonical cases:
- Malformed JSON (syntax error, unclosed bracket)
- Wrong Content-Type header (sending form-encoded data to an endpoint expecting JSON)
- Missing required HTTP-level metadata
- Request body that can’t be parsed at all
400 is a transport-level or parsing-level error. The server didn’t get far enough to evaluate the content of the request because the form of the request was broken.
POST /api/users
Content-Type: application/json
{name: "Alice", age: 30} ← Invalid JSON (unquoted keys)
→ 400 Bad Request
422 Unprocessable Entity
The server understands the content type of the request entity, and the syntax of the request entity is correct, but it was unable to process the contained instructions.
— RFC 4918 (WebDAV), now referenced in RFC 9110
422 means: I understood the request, but the content is semantically invalid. The server parsed the body successfully but cannot act on it because of business logic or data validation failures:
- Valid JSON, but
emailis not a valid email address format - Valid JSON, but
ageis -5 (negative) - Valid JSON, but references a resource ID that doesn’t exist in a context where it must
- Valid JSON, but the combination of fields is contradictory
POST /api/users
Content-Type: application/json
{"name": "Alice", "email": "not-an-email", "age": -5}
→ 422 Unprocessable Entity
{
"errors": [
{"field": "email", "message": "must be a valid email address"},
{"field": "age", "message": "must be a positive integer"}
]
}
The body was parsed. The server understood what you were trying to do. The data just doesn’t pass validation.
The practical distinction
| Situation | Code |
|---|---|
| JSON syntax error | 400 |
| Wrong Content-Type | 400 |
| Missing required HTTP header | 400 |
| Valid JSON, field fails format validation | 422 |
| Valid JSON, field fails business rule | 422 |
| Valid JSON, references nonexistent related resource | 422 or 404* |
| Valid JSON, violates uniqueness constraint | 409 |
*Whether a nonexistent related resource is 404 or 422 is a matter of convention. If the endpoint is /api/orders and you send {"user_id": 9999} where user 9999 doesn’t exist, some APIs return 404 (not found), some return 422 (validation failure: user_id references a user that doesn’t exist). 422 is more informative when paired with a field-level error message.
What the frameworks default to
Ruby on Rails: Rails returns 422 for model validation failures. If a model has validates :email, format: ... and the email is invalid, Rails renders a 422 by default from scaffold controllers.
Django REST Framework: DRF returns 400 for serializer validation failures, even when the JSON is syntactically valid. The DRF maintainers’ position is that “invalid data = bad request.” You can override this by customizing the exception handler.
FastAPI (Python): FastAPI returns 422 for request body validation failures (via Pydantic), matching the OpenAPI specification, which uses 422 as the standard validation error code.
Express (Node.js): No convention — you choose. Many Express APIs use 400 for everything; some use 422 for semantic errors.
Spring Boot: By default returns 400 for @Valid constraint violations via MethodArgumentNotValidException. You can customize to 422.
The practical upshot: if you’re building a public API that will be consumed by third parties, pick one approach and document it. If you’re building an internal API with controlled consumers, consistency matters more than which code you pick.
Other 4xx codes worth using correctly
401 vs 403
This is the other major source of confusion:
- 401 Unauthorized: the request lacks valid authentication credentials. “You haven’t identified yourself.” Despite the name, it means unauthenticated, not unauthorized.
- 403 Forbidden: the server understood the request and knows who you are, but refuses to authorize it. “You’re identified, but not allowed.”
GET /admin/dashboard
(no auth token)
→ 401 "Who are you? Provide credentials."
GET /admin/dashboard
Authorization: Bearer <valid user token> (user is not an admin)
→ 403 "I know who you are. You don't have permission."
A subtle point: 401 responses must include a WWW-Authenticate header per RFC 9110. Many APIs skip this and still return 401 — technically incorrect, but universally accepted.
404 vs 410
- 404 Not Found: the resource doesn’t exist at this URL. It may never have existed, or may have existed and been deleted. The server isn’t saying which.
- 410 Gone: the resource existed here and is permanently gone. Use this when you know the URL is obsolete. Clients (and search engines) can remove it from caches.
410 is underused. Most APIs return 404 for deleted resources when they could return 410 and help clients clean up stale bookmarks.
409 Conflict
Use 409 when the request is valid but conflicts with the current state of the server:
- Creating a user with an email address that already exists (
UNIQUEconstraint) - Optimistic locking conflict (you submitted an update with an outdated
versionfield) - Completing an order that’s already been completed
POST /api/users
{"email": "alice@example.com"} ← already registered
→ 409 Conflict
{"error": "email_taken", "message": "An account with this email already exists."}
415 Unsupported Media Type
When the client sends Content-Type: text/plain to an endpoint that requires application/json. This is more specific than 400 — the server isn’t complaining about the content, just the declared format.
429 Too Many Requests
Rate limiting. Include a Retry-After header with the number of seconds until the client can try again. This is machine-actionable — clients can back off automatically when they see it.
The error response body
The HTTP status code conveys the category of failure. The body should convey the specifics — ideally in a machine-readable format.
The RFC 9457 (Problem Details for HTTP APIs) format is the current standard:
{
"type": "https://example.com/errors/validation",
"title": "Validation Failed",
"status": 422,
"detail": "2 fields failed validation.",
"instance": "/api/users",
"errors": [
{"pointer": "/email", "detail": "must be a valid email address"},
{"pointer": "/age", "detail": "must be a positive integer"}
]
}
The pointer field (a JSON Pointer, RFC 6901) tells the client exactly which field failed. This enables client-side form highlighting without string-parsing the error message.
For internal APIs
If you control both the client and server (a mobile app and your own backend, or microservices you own), the distinction between 400 and 422 matters much less. Pick one and be consistent. The important things are:
- Return field-level error detail in the body
- Use 409 for state conflicts (not 400)
- Use 401 vs 403 correctly — clients often need to distinguish “need to log in” from “need different permissions”
- Return 429 for rate limiting so clients can back off
Further reading
- HTTP Status Codes reference — full RFC-cited reference for all status codes
- RFC 9110 — HTTP Semantics — the authoritative spec
- RFC 9457 — Problem Details for HTTP APIs — standardized error response format
- httpstatuses.com — quick reference with RFC quotes
Related posts
- HTTP 301 vs 302 Redirect — Permanent vs Temporary Redirects Explained — Understand when to use 301 (permanent) vs 302 (temporary) redirects, how they af…
- HTTP 404 Not Found — What It Means and How to Fix It — HTTP 404 means the server understood the request but can't find the resource. It…
- HTTP 500 Internal Server Error — What It Means and How to Debug It — HTTP 500 means the server encountered an unexpected error it couldn't handle. Un…
Related tool
Full HTTP status code reference with explanations and when to use each.
Written by Mian Ali Khalid. Part of the Dev Productivity pillar.