Skip to content
Control Plane Labs

422 Unprocessable Content

Client error response defined by RFC 9110 §15.5.21.

Written and maintained by Ben Ennis

Last reviewed July 27, 2026 · How we verify this

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/users

What 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.

Why a 422 must never be served from cache

RFC 9111 §4.2.2 lists the response status codes a cache may store and reuse without explicit freshness information: 200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414, and 501. 422 is not on that list, and neither is 400 or 409. That omission is not an oversight — a validation failure is a judgement about the specific request body you sent, not a durable fact about the URL, so there is nothing for a shared cache to usefully reuse. Two different POST bodies to the same endpoint can produce completely different 422 outcomes, unlike a 404 or 410 that describes the resource itself regardless of what request produced it.

The practical consequence is that a 422 can only end up cached if a server explicitly opts in with Cache-Control: max-age or similar, and doing so is almost always a bug. A CDN or reverse-proxy cache configured to cache “any successful-looking response regardless of status” will not touch a 422 by default, but a misconfigured rule that caches by path while ignoring method and body will happily replay somebody else’s validation error to an entirely different request. If you see a 422 response carrying an Age header, or a client reporting the identical validation error across requests it knows differ, look at cache configuration before assuming the validation logic itself is broken — a non-cacheable status code showing cache metadata is a configuration bug, not a validation bug.

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