Fix: FastAPI 422 Unprocessable Entity (validation error)
Quick Answer
How to fix FastAPI 422 Unprocessable Entity error caused by wrong request body format, missing fields, type mismatches, query parameter errors, and Pydantic validation.
The Error
You call a FastAPI endpoint and get:
{
"detail": [
{
"type": "missing",
"loc": ["body", "name"],
"msg": "Field required",
"input": {}
}
]
}With HTTP status 422 Unprocessable Entity.
Or variations:
{
"detail": [
{
"type": "string_type",
"loc": ["body", "age"],
"msg": "Input should be a valid string",
"input": 25
}
]
}{
"detail": [
{
"type": "missing",
"loc": ["query", "page"],
"msg": "Field required"
}
]
}FastAPI validated the incoming request data against the expected schema and found errors. The request body, query parameters, or path parameters do not match what the endpoint expects.
Why This Happens
FastAPI uses Pydantic models to validate all incoming data automatically. When the data does not match the declared types and constraints, FastAPI returns a 422 error with details about what is wrong.
The 422 response includes:
loc— Where the error is:["body", "field_name"],["query", "param_name"], or["path", "param_name"].msg— Human-readable error description.type— Error type code.input— The actual value that was received.
Common causes:
- Missing required fields. The request body is missing a field the model requires.
- Wrong data type. Sending a string where an integer is expected.
- Wrong Content-Type. Sending form data instead of JSON, or vice versa.
- Sending data in the wrong place. Query param instead of body, or body instead of path param.
- Nested model validation. A nested object has invalid fields.
- Enum or constraint violations. A value outside the allowed range or not in the enum.
Fix 1: Send the Correct Request Body
Match your request to the Pydantic model:
Endpoint definition:
from pydantic import BaseModel
class UserCreate(BaseModel):
name: str
email: str
age: int
@app.post("/users/")
async def create_user(user: UserCreate):
return {"user": user}Broken — missing required field:
curl -X POST http://localhost:8000/users/ \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "email": "alice@example.com"}'
# 422: age is requiredFixed — include all required fields:
curl -X POST http://localhost:8000/users/ \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "email": "alice@example.com", "age": 30}'Broken — wrong type:
curl -X POST http://localhost:8000/users/ \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "email": "alice@example.com", "age": "thirty"}'
# 422: age should be a valid integerPro Tip: FastAPI auto-generates interactive API docs at
/docs(Swagger UI) and/redoc. Openhttp://localhost:8000/docsto see the exact schema for every endpoint and test requests interactively.
Fix 2: Set the Correct Content-Type
FastAPI expects JSON by default for request bodies:
Broken — missing Content-Type:
curl -X POST http://localhost:8000/users/ \
-d '{"name": "Alice"}'
# 422: FastAPI can't parse the body as JSONFixed — add Content-Type header:
curl -X POST http://localhost:8000/users/ \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "email": "alice@example.com", "age": 30}'In JavaScript:
// Wrong — sends form data by default
fetch('/users/', {
method: 'POST',
body: JSON.stringify({ name: 'Alice', email: 'alice@example.com', age: 30 }),
});
// Fixed — set Content-Type
fetch('/users/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Alice', email: 'alice@example.com', age: 30 }),
});For form data, use Form instead of Pydantic models:
from fastapi import Form
@app.post("/login/")
async def login(username: str = Form(), password: str = Form()):
return {"username": username}curl -X POST http://localhost:8000/login/ \
-d "username=alice&password=secret"Fix 3: Fix Optional Fields
Make fields optional with default values:
from typing import Optional
from pydantic import BaseModel
class UserCreate(BaseModel):
name: str # Required
email: str # Required
age: Optional[int] = None # Optional, defaults to None
bio: str = "" # Optional, defaults to empty string
role: str = "user" # Optional with defaultNow these all work:
{"name": "Alice", "email": "alice@example.com"}
{"name": "Alice", "email": "alice@example.com", "age": 30}
{"name": "Alice", "email": "alice@example.com", "age": null, "bio": "Hello"}Fix 4: Fix Query Parameter Errors
Query parameters have the same validation:
@app.get("/users/")
async def list_users(page: int = 1, limit: int = 10):
return {"page": page, "limit": limit}Broken — wrong type in query param:
curl "http://localhost:8000/users/?page=abc"
# 422: page should be a valid integerFixed:
curl "http://localhost:8000/users/?page=2&limit=20"Optional query parameters:
from typing import Optional
@app.get("/search/")
async def search(
q: str, # Required query param
page: int = 1, # Optional with default
category: Optional[str] = None, # Optional, can be omitted
):
return {"q": q, "page": page, "category": category}Fix 5: Fix Path Parameter Errors
@app.get("/users/{user_id}")
async def get_user(user_id: int):
return {"user_id": user_id}Broken:
curl http://localhost:8000/users/abc
# 422: user_id should be a valid integerFixed:
curl http://localhost:8000/users/123Use Path for validation:
from fastapi import Path
@app.get("/users/{user_id}")
async def get_user(user_id: int = Path(gt=0, description="User ID")):
return {"user_id": user_id}Fix 6: Fix Nested Model Validation
Nested Pydantic models validate recursively:
class Address(BaseModel):
street: str
city: str
zip_code: str
class UserCreate(BaseModel):
name: str
address: Address
@app.post("/users/")
async def create_user(user: UserCreate):
return userBroken — missing nested field:
{
"name": "Alice",
"address": {
"street": "123 Main St",
"city": "Springfield"
}
}{
"detail": [
{
"loc": ["body", "address", "zip_code"],
"msg": "Field required"
}
]
}Fixed:
{
"name": "Alice",
"address": {
"street": "123 Main St",
"city": "Springfield",
"zip_code": "62704"
}
}Fix 7: Add Custom Validation
Use Pydantic validators for complex rules:
from pydantic import BaseModel, field_validator, EmailStr
class UserCreate(BaseModel):
name: str
email: EmailStr
age: int
@field_validator('name')
@classmethod
def name_must_not_be_empty(cls, v):
if not v.strip():
raise ValueError('Name cannot be empty')
return v.strip()
@field_validator('age')
@classmethod
def age_must_be_valid(cls, v):
if v < 0 or v > 150:
raise ValueError('Age must be between 0 and 150')
return vUse Field constraints:
from pydantic import BaseModel, Field
class UserCreate(BaseModel):
name: str = Field(min_length=1, max_length=100)
email: str = Field(pattern=r'^[\w.-]+@[\w.-]+\.\w+$')
age: int = Field(ge=0, le=150)Fix 8: Customize Error Responses
Override the default 422 response format:
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
app = FastAPI()
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
errors = []
for error in exc.errors():
field = " -> ".join(str(loc) for loc in error["loc"])
errors.append({
"field": field,
"message": error["msg"],
})
return JSONResponse(
status_code=422,
content={
"error": "Validation failed",
"details": errors,
},
)Still Not Working?
Check the /docs endpoint. FastAPI’s auto-generated Swagger UI shows the exact expected schema for every endpoint. Test your request there first.
Check for Pydantic v1 vs v2 differences. FastAPI 0.100+ uses Pydantic v2 by default. Some model definitions changed:
# Pydantic v1
class User(BaseModel):
class Config:
orm_mode = True
# Pydantic v2
class User(BaseModel):
model_config = ConfigDict(from_attributes=True)Check for file upload issues. File uploads need File and UploadFile:
from fastapi import File, UploadFile
@app.post("/upload/")
async def upload(file: UploadFile = File()):
return {"filename": file.filename}For Python async errors, see Fix: Python RuntimeError: no running event loop. For JSON parsing errors, see Fix: Python JSONDecodeError: Expecting value.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: AWS Lambda Unable to import module / Runtime.ImportModuleError
How to fix the AWS Lambda Runtime.ImportModuleError and Unable to import module error caused by wrong handler paths, missing dependencies, layer issues, and packaging problems.
Fix: Python TypeError: unhashable type: 'list'
Learn why Python raises TypeError unhashable type list, dict, or set and how to fix it when using dictionary keys, sets, groupby, dataclasses, and custom classes.
Fix: Django Forbidden (403) CSRF verification failed
How to fix Django 403 CSRF verification failed error caused by missing CSRF tokens, AJAX requests, cross-origin issues, HTTPS misconfig, and session problems.
Fix: Python RuntimeError: no running event loop / This event loop is already running
How to fix Python asyncio RuntimeError no running event loop and event loop already running caused by mixing sync and async code, Jupyter, and wrong loop management.