On this page

← Blog
Engineering6 min read

Fix FastAPI 422 Unprocessable Entity: 6 Causes and Fixes

FastAPI 422, whether your version calls it Unprocessable Entity or Unprocessable Content, means the request failed Pydantic validation before your route ran. The detail array names the exact field — here are the six causes and the fix for each.

fastapipydantic422validationpythonapi

FastAPI returns 422 when the request body does not match the Pydantic model defined in your route. The error comes from the validation layer, not your business logic, so your handler code never runs.

The response body tells you exactly what failed. Read it before changing anything.

Reading the 422 Response

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

The detail array has one object per validation failure:

  • loc is the path to the field that failed. ["body", "email"] means the email field in the request body. ["body", "user", "age"] means nested: request body, then user object, then age field.
  • msg is the human-readable reason.
  • type is the machine-readable error code.

Start with loc. That tells you which field. Then read msg for what is wrong with it.

422 Unprocessable Entity or Unprocessable Content?

Both names describe the same status code. HTTP semantics (RFC 9110) renamed 422 from Unprocessable Entity to Unprocessable Content, and Starlette adopted the newer phrase, so a recent FastAPI returns 422 Unprocessable Content where an older one returned 422 Unprocessable Entity.

Nothing about the cause or the fix changes. The detail array is identical and everything below applies to both.

Cause 1: Missing Required Field

python
class UserCreate(BaseModel):
    email: str
    password: str
    name: str

Sending a body without name:

json
{ "email": "user@example.com", "password": "secret" }

Returns 422 with field required for name. Either send the field, or make it optional:

python
from typing import Optional

class UserCreate(BaseModel):
    email: str
    password: str
    name: Optional[str] = None

Cause 2: Wrong Type

python
class Item(BaseModel):
    quantity: int
    price: float

Sending:

json
{ "quantity": "five", "price": 9.99 }

Returns 422 with value is not a valid integer for quantity.

Note: FastAPI coerces types automatically in some cases. Sending "5" for an int field works because it can be converted. "five" cannot, so it fails.

If your client always sends strings, add a validator to coerce before validation runs:

python
from pydantic import validator

class Item(BaseModel):
    quantity: int
    price: float

    @validator('quantity', pre=True)
    def parse_quantity(cls, v):
        return int(v)

Cause 3: Nested Model Mismatch

python
class Address(BaseModel):
    street: str
    city: str
    zip_code: str

class User(BaseModel):
    email: str
    address: Address

Sending a flat body instead of nested:

json
{ "email": "user@example.com", "street": "123 Main St" }

Returns 422 with address field required. Send the correct structure:

json
{
  "email": "user@example.com",
  "address": {
    "street": "123 Main St",
    "city": "Portland",
    "zip_code": "97201"
  }
}

Cause 4: Query Param vs Body Confusion

python
@app.post("/users")
async def create_user(email: str, password: str):
    ...

Plain function parameters in a POST route are treated as query params, not body. Sending a JSON body with email and password returns 422 because FastAPI expects them at /users?email=...&password=....

Use a Pydantic model for body parameters:

python
class UserCreate(BaseModel):
    email: str
    password: str

@app.post("/users")
async def create_user(user: UserCreate):
    ...

Cause 5: Enum Value Not in Allowed List

python
from enum import Enum

class Role(str, Enum):
    admin = "admin"
    user = "user"
    viewer = "viewer"

class UserCreate(BaseModel):
    role: Role

Sending "superadmin" returns 422 with value is not a valid enumeration member. Send one of the values defined in the Enum class: "admin", "user", or "viewer".

Cause 6: Missing Required Header

A header declared with Header() is required by default. A request without it returns 422 — not 401, and not 400:

python
from fastapi import Header

@app.post("/items")
async def create_item(x_api_key: str = Header()):
    ...

Calling /items with no X-API-Key header returns:

json
{
  "detail": [
    {
      "loc": ["header", "x-api-key"],
      "msg": "field required",
      "type": "value_error.missing"
    }
  ]
}

loc[0] is "header", which is how you tell this apart from a body problem at a glance. FastAPI converts underscores in the parameter name to hyphens, so x_api_key maps to the X-API-Key header — a mismatch here looks like a missing header even when the client sent one.

To make it optional, give it a default:

python
async def create_item(x_api_key: str | None = Header(default=None)):
    ...

Debug 422 Locally

Add a validation exception handler during development. It logs the full incoming body next to the validation errors so you can see exactly what was sent vs what was expected:

python
from fastapi import Request
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse

@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
    print(f"422 on {request.url}")
    print(f"Body: {await request.body()}")
    print(f"Errors: {exc.errors()}")
    return JSONResponse(status_code=422, content={"detail": exc.errors()})

FAQ

Q: Why does 422 happen even when I send the correct JSON?

A: Check the Content-Type header. FastAPI expects application/json. Sending without it, or with application/x-www-form-urlencoded, means the body is not parsed as JSON and all fields appear missing.

Q: Can I turn off Pydantic validation for a specific route?

A: Not directly. If you need to accept arbitrary data, use request: Request and read await request.json() manually. You lose validation but get full control over the raw body.


For 422 errors in complex nested models or when validation logic spans multiple validators, paste the model definition and the request body into DebugAI and it will show exactly which field failed and why.

Debug faster starting today.

Free VS Code extension · 10 sessions/day · no credit card

Install free →

Related posts

Engineering

Not one team could verify an AI fix in a single deploy.

8 min read

Engineering

Your coding agent is guessing which file to edit.

7 min read

← All posts