Python

Python FastAPI "422 Unprocessable Entity" on Valid JSON Payload

· by DebuggedIt

Quick answer

FastAPI's 422 response always includes a detailed body explaining exactly which field failed validation and why — the fix almost always starts with actually...

FastAPI's 422 response always includes a detailed body explaining exactly which field failed validation and why — the fix almost always starts with actually reading that response body, since the specific mismatch between your payload and the expected Pydantic model is usually immediately clear once you look at the actual error detail rather than just the status code.

The Problem

A request with what looks like a correctly formatted JSON payload is rejected:

422 Unprocessable Entity

The payload appears syntactically valid JSON and seems to match the expected fields, which makes the rejection confusing without inspecting the actual, detailed error response body FastAPI includes alongside the status code.

Why It Happens

FastAPI (via Pydantic) validates incoming request bodies against your declared model, and returns 422 specifically when the payload doesn't match that model's expected structure or types — the response body accompanying this status code contains a detailed, field-by-field breakdown of exactly what's wrong, which is essential information that's easy to overlook if you're only checking the status code:

{
  "detail": [
    {
      "loc": ["body", "email"],
      "msg": "field required",
      "type": "value_error.missing"
    }
  ]
}

Common specific causes behind this detailed error:

  • A required field is missing from the payload, or its name doesn't exactly match the field name declared in the Pydantic model (including case sensitivity)
  • A field's value has the wrong type — sending a string where the model expects an integer, or vice versa, without FastAPI's automatic type coercion being able to bridge the specific mismatch
  • The Content-Type header isn't set to application/json, causing FastAPI to fail to parse the body as JSON at all, which then cascades into every declared field appearing "missing" from FastAPI's perspective
  • Nested model validation failing on a sub-object or list item, where the top-level structure looks correct but something deeper within it doesn't match the expected nested model

The Fix

Always inspect the actual response body, not just the status code, when debugging a 422 — it contains the exact field(s) and reason(s) for the validation failure:

curl -X POST http://localhost:8000/users \
  -H "Content-Type: application/json" \
  -d '{"name": "Alice"}' | python3 -m json.tool

The loc field in each error entry tells you exactly which field (and its path, for nested structures) failed, and msg/type tell you why — this is almost always sufficient to immediately identify the specific mismatch, rather than needing to guess.

Compare the exact field names in your payload against your Pydantic model definition, checking for a typo or case mismatch:

from pydantic import BaseModel

class UserInput(BaseModel):
    name: str
    email: str  # if your payload sends "Email" or "email_address" instead, this fails

Confirm the request's Content-Type header is correctly set to application/json — a missing or incorrect header can cause FastAPI to fail parsing the body entirely, which then manifests as every field appearing missing, even though the actual JSON content itself might be perfectly well-formed:

curl -X POST http://localhost:8000/users \
  -H "Content-Type: application/json" \
  -d '{"name": "Alice", "email": "alice@example.com"}'

For type mismatches, either correct the payload to send the expected type, or, if some flexibility is genuinely intended (accepting either a string or number for a particular field, for example), adjust the Pydantic model to explicitly support the range of types you actually want to accept:

from typing import Union

class FlexibleInput(BaseModel):
    quantity: Union[int, str]  # explicitly allows either type, if that's genuinely intended

For nested validation failures, use the loc path from the error response to navigate directly to the specific nested field or list item that's failing, rather than trying to validate the entire payload structure manually by eye:

{
  "detail": [
    {
      "loc": ["body", "items", 2, "price"],
      "msg": "value is not a valid float",
      "type": "type_error.float"
    }
  ]
}

This specific error, for example, points directly at index 2 of an items list, specifically its price field — much faster to fix once you know exactly where to look, rather than reviewing the entire payload structure from scratch.

Still Not Working?

If the response body's error detail doesn't clearly explain the issue, or you're testing through a tool/client that doesn't show you the full response body by default, use FastAPI's automatically generated interactive documentation (available by default at /docs) to test the exact same endpoint with the "Try it out" feature — this shows you both the exact expected schema for the request body and a clear, formatted validation error if your test input doesn't match, which is often a faster and more informative debugging path than working purely from raw curl commands and manually parsing JSON error responses.