Parameter Validation
FastAPI validates every path and query parameter against its type hint and any Pydantic constraints before your function runs. Invalid input never reaches your code - it becomes a structured 422 response with one entry per problem.
What a 422 Looks Like
{
"detail": [
{
"type": "int_parsing",
"loc": ["query", "limit"],
"msg": "Input should be a valid integer, unable to parse string as an integer",
"input": "abc"
}
]
}
Field Constraints
Annotated + Field is the standard way to add rules to scalar parameters:
from typing import Annotated
from pydantic import Field
def read_items(
skip: Annotated[int, Field(ge=0, le=1000)] = 0,
limit: Annotated[int, Field(ge=1, le=100)] = 10,
tag: Annotated[str, Field(min_length=1, max_length=30)] = "all",
):
return {"skip": skip, "limit": limit, "tag": tag}
Common constraints: ge/le (greater/less or equal), gt/lt, min_length/max_length, pattern (regex), multiple_of.
Required vs Optional
A parameter without a default is required; one with a default is optional. For an explicitly optional value, use the None type:
def search(q: str | None = None, limit: int = 10):
return {"q": q, "limit": limit} # q is optional, limit has a default
Custom Error Responses
When you need a different message or status code than 422, raise HTTPException:
from fastapi import HTTPException
@app.get("/items/{item_id}")
def read_item(item_id: int):
if item_id < 0:
raise HTTPException(status_code=404, detail="Item not found")
return {"item_id": item_id}
Summary
Validation in FastAPI is declarative: type hints convert values, Annotated + Field adds constraints, missing required values and failed constraints produce a structured 422, and HTTPException lets you override the response when your domain logic demands a different status or message.