422 Unprocessable Content
Client error response defined by RFC 9110 §15.5.21.
Last updated July 27, 2026
Common causes at a glance
- JSON parsed fine but a field failed a validation rule
- A required property missing, or an enum given an unknown value
- A framework default: Rails, Laravel and FastAPI all return 422 for validation errors
- Business-rule rejection such as a duplicate email or an out-of-range date
Reproduce it
curl -i -X POST -H 'Content-Type: application/json' -d '{"email":"nope"}' https://api.example.com/v1/usersWhat 422 Unprocessable Content means
The distinction 422 draws is between parsing and understanding. A body that
is not valid JSON at all earns 400; a body the server cannot parse
because it does not speak the media type earns 415; a body that
parses cleanly but says "age": -3 earns 422. The payload simply does not
describe a state the server is willing to accept.
RFC 9110 renamed the code. WebDAV registered it as “Unprocessable Entity” and that phrase is still what most libraries print, but the currently registered phrase is “Unprocessable Content”. Servers may send either; clients must key off the number.
What the spec says
422 was originally RFC 4918 §11.2 and applied to XML
request bodies in WebDAV. RFC 9110 §15.5.21 generalised it to any
content type and renamed it. The definition is deliberately thin: the content type was
understood, the syntax is correct, and the instructions could not be processed. Nothing in
the spec dictates the error body. That gap is what RFC 9457 fills — its
application/problem+json object, with the extension members in
§3.2, is the closest thing to a standard shape for validation
output.
What actually causes it
Most 422s in the wild come from a framework’s default rather than a
deliberate design decision. Rails returns 422 from
render json: obj.errors, status: :unprocessable_entity, Laravel’s
ValidationException does the same, and FastAPI returns 422 for any Pydantic
model that fails to validate — including a malformed path parameter, which is arguably a
400. That fuels a long-running argument. One camp reads RFC 9110 narrowly and says 400
covers everything a client got wrong; the other wants the extra signal so a client can
distinguish “retry with different data” from “your request was garbage”. Pick one and
stay consistent, because clients will branch on it.
How to debug it
Read the body before anything else. A well-behaved 422 names the field that failed, and the response is where all the information lives — there is no header convention for validation errors.
curl -sS -i -X POST -H 'Content-Type: application/json' \
-d '{"email":"nope","age":-3}' https://api.example.com/v1/users
If the body is empty or a generic string, the next question is whether the request even
reached the handler. Framework-level validation rejects before application code runs, so
your own logs may show nothing. Confirm the Content-Type is what the server
expects: sending application/x-www-form-urlencoded to a JSON endpoint often
yields 422 rather than 415, because the parser produced an empty object that then failed
validation. Then diff a known-good payload against the failing one field by field.
Working examples
curl
# The body is the whole point of a 422. Print it, and the headers with it.
curl -sS -i -X POST \
-H 'Content-Type: application/json' \
-d '{"email":"nope","age":-3}' \
https://api.example.com/v1/users
# Compare against a payload you know is valid to isolate the offending field.
Python (requests)
import requests
resp = requests.post(
"https://api.example.com/v1/users",
json={"email": "nope", "age": -3},
timeout=10,
)
if resp.status_code == 422:
# RFC 9457 problem+json if you are lucky, framework-specific JSON if not.
print(resp.headers.get("Content-Type"))
print(resp.json()) # e.g. {"errors": {"email": ["is invalid"]}}
resp.raise_for_status()
Node (fetch)
const res = await fetch("https://api.example.com/v1/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: "nope", age: -3 }),
});
if (res.status === 422) {
const problem = await res.json();
// Do not retry the same body; nothing about it will succeed on a second attempt.
console.error("validation failed:", problem);
}Reference and tooling
The authoritative list of assigned status codes is theIANA HTTP Status Code Registry. For a searchable copy with the semantics summarised, use theHTTP status code reference on this site. To see exactly what a server is sending, theheader inspector shows the raw status line and response headers, and thecurl builder assembles the request flags for you.
Other 4xx codes
- 400 Bad Request
- 401 Unauthorized
- 402 Payment Required
- 403 Forbidden
- 404 Not Found
- 405 Method Not Allowed
- 406 Not Acceptable
- 407 Proxy Authentication Required
- 408 Request Timeout
- 409 Conflict
- 410 Gone
- 411 Length Required
- 412 Precondition Failed
- 413 Content Too Large
- 414 URI Too Long
- 415 Unsupported Media Type
- 416 Range Not Satisfiable
- 417 Expectation Failed
- 418 (Unused)
- 419 Page Expired
- 421 Misdirected Request
- 423 Locked
- 424 Failed Dependency
- 425 Too Early
- 426 Upgrade Required
- 428 Precondition Required
- 429 Too Many Requests
- 431 Request Header Fields Too Large
- 444 No Response
- 451 Unavailable For Legal Reasons
- 494 Request Header Too Large
- 495 SSL Certificate Error
- 496 SSL Certificate Required
- 497 HTTP Request Sent to HTTPS Port
- 499 Client Closed Request