Skip to content
Control Plane Labs

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

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