Engineering7 min read

Fix FastAPI 422 Unprocessable Entity — 5 Causes With Code

FastAPI 422 means your request body failed Pydantic validation. Here are the 5 most common causes — wrong types, missing fields, nested models — and the exact fix for each.

FastAPIPython422 errorPydanticdebugging

What Does FastAPI 422 Mean?

FastAPI 422 Unprocessable Entity means your request body, query parameters, or path parameters failed Pydantic validation. FastAPI rejected the request before your route function even ran.

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

The response body is always a JSON object with a detail array. Each item in detail tells you exactly what failed: the location (loc), the message (msg), and the type (type). Read the detail array first — it's more useful than the 422 status code.

How to Read the Error Detail

json
{
  "detail": [
    {
      "loc": ["body", "user", "age"],
      "msg": "value is not a valid integer",
      "type": "type_error.integer"
    }
  ]
}

- loc = where the problem is. ["body", "user", "age"] means: in the request body → inside the user object → in the age field.
- msg = human-readable what's wrong
- type = Pydantic error type

▎ Note: When loc[0] is "query", the problem is a query parameter, not the body. When it's "path", it's a path parameter. The fix approach differs
▎ for each.

Cause 1: Missing Required Field

Pydantic model fields without a default value are required. Sending a request without them = 422.

# Model expects all three fields — no defaults
class CreateUser(BaseModel):
    name: str
    email: str
    role: str

# ❌ Request body missing 'role'
# POST /users
# {"name": "Alice", "email": "alice@example.com"}
# → 422: field required at ["body", "role"]

▎ Fix: Either add the field to your request, or give it a default in the model.

from typing import Optional

class CreateUser(BaseModel):
    name: str
    email: str
    role: str = "member"  # Default — field now optional

Cause 2: Wrong Type Sent

Pydantic validates types strictly. Sending "25" (string) for an int field fails, even though Python itself would convert it.

class UpdateProfile(BaseModel):
    user_id: int
    age: int

# ❌ age sent as string
# {"user_id": 1, "age": "twenty-five"}
# → 422: value is not a valid integer at ["body", "age"]

▎ Fix: Fix the client to send the right type, OR use Pydantic's coercion by using validator or switching to int | str with a validator.

from pydantic import validator

class UpdateProfile(BaseModel):
    user_id: int
    age: int

    @validator('age', pre=True)
    def coerce_age(cls, v):
        return int(v)  # Accepts "25" and converts to 25

▎ Warning: Don't add coercion validators blindly. They hide client bugs. Fix the client first. Add coercion only when you genuinely can't control
▎ input format (e.g., third-party webhooks).

Cause 3: Nested Model Validation Failure

If your model contains nested models, 422 errors deep in the hierarchy have long loc paths that are easy to misread.

class Address(BaseModel):
    street: str
    city: str
    postcode: str

class CreateOrder(BaseModel):
    item_id: int
    quantity: int
    shipping_address: Address

# ❌ postcode missing from nested Address
# {"item_id": 5, "quantity": 2, "shipping_address": {"street": "1 Main St", "city": "London"}}
# → 422: field required at ["body", "shipping_address", "postcode"]

▎ Fix: Follow the loc path exactly. ["body", "shipping_address", "postcode"] → go to the shipping_address object in your request body → add
▎ postcode.

{
  "item_id": 5,
  "quantity": 2,
  "shipping_address": {
    "street": "1 Main St",
    "city": "London",
    "postcode": "EC1A 1BB"
  }
}

Cause 4: Query Parameter Type Mismatch

Query parameters are always strings in HTTP. FastAPI converts them automatically, but the conversion can fail.

@app.get("/items")
async def get_items(page: int, limit: int = 20):
    ...

# ❌ Request: GET /items?page=first
# → 422: value is not a valid integer at ["query", "page"]

▎ Fix: Validate query params on the client before sending. For optional params, use Optional[int] = None to allow missing values.

from typing import Optional

@app.get("/items")
async def get_items(page: Optional[int] = 1, limit: Optional[int] = 20):
    ...

Cause 5: Sending JSON as Form Data (or Vice Versa)

FastAPI distinguishes between JSON body (Content-Type: application/json) and form data (Content-Type: application/x-www-form-urlencoded). Sending
JSON when the route expects form data — or the reverse — always causes 422.

# Route expects Form data
from fastapi import Form

@app.post("/login")
async def login(username: str = Form(...), password: str = Form(...)):
    ...

# ❌ Client sends JSON instead of form data
# Content-Type: application/json
# {"username": "alice", "password": "secret"}
# → 422

▎ Fix: Match Content-Type to what the route expects. For Form routes, send Content-Type: application/x-www-form-urlencoded. For JSON routes, use
▎ Content-Type: application/json and a Pydantic model as the body param.

# Route expects JSON body — use BaseModel
class LoginRequest(BaseModel):
    username: str
    password: str

@app.post("/login")
async def login(body: LoginRequest):
    ...

Debug 422 Errors in 30 Seconds

FastAPI's auto-generated docs at /docs (Swagger UI) let you test requests interactively. Use "Try it out" to see exactly what FastAPI expects —
the schema is generated directly from your Pydantic models, so it's always accurate.

For 422s in production, log the full request body alongside the validation error:

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):
    body = await request.body()
    print(f"Validation error. Body: {body}. Errors: {exc.errors()}")
    return JSONResponse(status_code=422, content={"detail": exc.errors()})

This logs the raw request body and the Pydantic validation errors together — makes root cause obvious without needing to reproduce the request
manually.

Summary

┌────────────────────────┬───────────────────────────────────────────┬──────────────────────────────────────────────┐
│         Cause          │            422 detail message             │                     Fix                      │
├────────────────────────┼───────────────────────────────────────────┼──────────────────────────────────────────────┤
│ Missing required field │ field required                            │ Add field to request or set default in model │
├────────────────────────┼───────────────────────────────────────────┼──────────────────────────────────────────────┤
│ Wrong type             │ value is not a valid integer/string/etc   │ Fix client type OR add validator             │
├────────────────────────┼───────────────────────────────────────────┼──────────────────────────────────────────────┤
│ Nested model failure   │ long loc path                             │ Follow loc path into nested object           │
├────────────────────────┼───────────────────────────────────────────┼──────────────────────────────────────────────┤
│ Query param type       │ value is not a valid integer at ["query"] │ Fix query string or use Optional             │
├────────────────────────┼───────────────────────────────────────────┼──────────────────────────────────────────────┤
│ Wrong content type     │ field required for all body fields        │ Match Content-Type to route definition       │
└────────────────────────┴───────────────────────────────────────────┴──────────────────────────────────────────────┘

When 422s appear in production from clients you don't control, use the exception handler above to log raw bodies. Paste the error + your Pydantic
model into DebugAI — it reads both and identifies the exact field mismatch without you having to compare schemas manually.

Debug faster starting today.

Free VS Code extension. 10 sessions/day. No credit card.

Install Free →

Related Posts

Engineering

How to Debug a Next.js Application in VS Code (App Router Guide)

8 min read

Engineering

Fix NameError in Python: "name 'X' is not defined" — 6 Causes and Fixes

6 min read

← All posts