FastAPI 422 Unprocessable Entity: What It Means and How to Fix It
A 422 from FastAPI is not a bug in your server — it's FastAPI telling you the request body or query didn't match your Pydantic model. The response JSON tells you exactly which field. Here's how to read it.
You call your endpoint and FastAPI hands back a 422:
{
"detail": [
{
"type": "missing",
"loc": ["body", "email"],
"msg": "Field required",
"input": {"name": "Ada"}
}
]
}
422 Unprocessable Entity means your request reached the endpoint fine, but the data failed validation against the Pydantic model. FastAPI rejects it before your function ever runs — so the fix is never inside your handler. It's in the mismatch between what you sent and what the model requires. The good news: the detail array names the exact problem.
Step 1: Read the loc and msg — they point straight at the field
Every item in detail has a loc (location) and a msg. Read loc right to left:
["body", "email"]+"Field required"— you didn't sendemailin the JSON body.["query", "limit"]+"Input should be a valid integer"—?limit=abcisn't a number.["body", "age"]+"Input should be greater than 0"— a constraint (gt=0) failed.["path", "item_id"]— the value in the URL path is the wrong type.
The first element of loc tells you where to look (body, query, path, header); the rest names the field.
Cause: you sent form data but the model expects JSON (or vice-versa)
This is the most common surprise. A plain Pydantic body model expects a JSON request:
curl -X POST /users -H "Content-Type: application/json" -d '{"name":"Ada","email":"[email protected]"}'
If you send -d name=Ada without the JSON header, FastAPI can't parse it into the model and you get 422. If you actually want form fields, you have to declare them with Form(...), not a Pydantic body model.
Cause: a required field is missing or misspelled
class User(BaseModel):
name: str
email: str # required — no default
@app.post("/users")
def create(user: User): ...
Send {"name": "Ada"} and email triggers "Field required". Either send the field, or make it optional with a default:
email: str | None = None
Cause: the type doesn't match
age: int will reject "age": "twenty". Pydantic will coerce "25" → 25, but it won't invent an integer from a word. Match the client's real payload to the model's types, or loosen the type.
The fastest way to debug it
Open /docs (FastAPI's built-in Swagger UI). It shows the exact schema each endpoint expects and lets you send a request that's guaranteed to match — if your hand-built request 422s but the /docs one works, the difference between them is your bug.
The checklist
- Read the
detailarray —loc+msgname the exact field and reason. - First element of
loc:body,query,path, orheader— that's where to fix it. - Sending JSON? Set
Content-Type: application/jsonand a JSON body. "Field required"→ send it, or give the model field a default.- Type mismatch → align the payload with the model, or relax the type.
- Reproduce it in
/docsto see the schema FastAPI actually expects.
Frequently Asked Questions
Why does FastAPI return 422 instead of 400?
FastAPI uses 422 Unprocessable Entity specifically for request-validation failures (the syntax was fine, the content didn't satisfy the schema). A 400 is reserved for a genuinely malformed request. You can override the handler if you prefer 400, but 422 is the intended, standards-based default.
Where is the actual error in a 422 response?
In the detail array. Each entry's loc field is the path to the offending value and msg explains what was wrong — read those two first, always.
How do I make a field optional so it stops 422-ing?
Give it a default in the Pydantic model, e.g. email: str | None = None. A field with no default is required, and its absence produces "Field required".
Stop reading, start building
This pairs with a hands-on BytExplorer course — do it on your own machine and actually keep the skill.