Search Topics
Search across all FastAPI topics
Request Body
Core ConceptRequest bodies let clients send JSON data to your API. FastAPI uses Pydantic models to automatically parse, validate, and document the expected shape.
Your mobile app sends a POST to create a user. The JSON has {"name": "Alice", "age": "twenty-five"}. FastAPI rejects it. The mobile developer says "It works in Postman!" — what's different?
POST /users
Content-Type: application/json
{"name": "Alice", "age": "twenty-five"}
→ 422 Unprocessable Entity
{
"detail": [{"type": "int_parsing", "loc": ["body", "age"], "msg": "Input should be a valid integer"}]
}Question
"It works in Postman" is the classic debugging red herring. Postman might be sending "25" (a string that Pydantic coerces to an int), while the mobile app sends "twenty-five" (which can't be coerced). FastAPI isn't being picky — it's catching data that would crash your database insert later.
How Request Body Validation Works
When JSON arrives, FastAPI reads it, tries to fit it into your Pydantic model, and either gives you a validated Python object or returns a detailed 422 error. Your code never touches invalid data.
JSON arrives
{"name": "Alice", "age": "twenty-five"}
Parse JSON
Valid JSON syntax? Yes
Validate model
"twenty-five" → int? Fails!
422 Response
Detailed error returned
JSON arrives
{"name": "Alice", "age": 25}
Parse JSON
Valid JSON syntax? Yes
Validate model
All fields valid
Run handler
create_user(user)
Request body basics
What you just learned
FastAPI reads JSON and validates it against your Pydantic model before your code runs
Invalid data returns a 422 with a detailed error — never reaches your function
Pydantic can coerce compatible types (string '25' to int 25) but not impossible ones
Declaring a Request Body
Create a Pydantic model with the fields you expect. Use it as a type hint in your function, and FastAPI handles the rest — parsing, validation, error messages, and OpenAPI docs. All from one class.
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
price: float
description: str | None = None # optional field
tax: float | None = None # optional field
app = FastAPI()
@app.post("/items")
async def create_item(item: Item):
# item is already validated — safe to use
total = item.price + (item.tax or 0)
return {**item.model_dump(), "total": total}Notice: description and tax have defaults, so they're optional in the JSON. Only name and price are required.
Watch Validation Happen
See how FastAPI validates incoming JSON against your Pydantic model — valid data gets parsed, bad data returns 422 errors.
Pydantic models
What you just learned
Pydantic models define the shape of your request body
Fields without defaults are required; fields with defaults are optional
model_dump() converts your validated model back to a dictionary
Mixing Body + Path + Query
Insight
You don't have to choose between body, path, and query parameters. FastAPI lets you combine all three in a single endpoint. It figures out which is which based on the same rules you already know.
@app.put("/items/{item_id}")
async def update_item(
item_id: int, # Path parameter (in the URL)
item: Item, # Request body (JSON)
q: str | None = None, # Query parameter (after ?)
):
result = {"item_id": item_id, **item.model_dump()}
if q:
result["q"] = q
return result
# PUT /items/42?q=search
# Body: {"name": "Widget", "price": 9.99}Go Deeper: Multiple Body Parameters
What if your endpoint needs two different models — say an Item and a User? FastAPI expects a nested JSON object where each key matches the parameter name.
class Item(BaseModel):
name: str
price: float
class User(BaseModel):
username: str
email: str
@app.post("/orders")
async def create_order(item: Item, user: User):
return {"item": item, "user": user}
# Expected body — note the nested structure:
# {
# "item": {"name": "Foo", "price": 42.0},
# "user": {"username": "john", "email": "john@example.com"}
# }Watch out
This catches people off guard. With a single body parameter, you send the fields directly. With multiple body parameters, FastAPI wraps them in an object keyed by parameter name. If your frontend sends a flat object and you expect two models, you'll get a 422.
Body parameters
What you just learned
Body, path, and query parameters can all coexist in one function
Multiple body parameters create a nested JSON structure
FastAPI auto-generates schema docs for all body parameters
Think about it...
If your Pydantic model has price: float and someone sends {"price": "9.99"} (a string), does FastAPI accept it? Why or why not?
Hint: Think about what Pydantic does with values that are 'close enough' to the declared type.
Key Points
Pydantic Models
Define request body shape using BaseModel subclasses
Auto Validation
Invalid data returns 422 with detailed error messages
Mixed Parameters
Combine body, path, and query params in one function
OpenAPI Docs
Body schema appears automatically in Swagger UI