Search Topics

Search across all FastAPI topics

GitHub

Request Body

Core Concept

Request 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?

terminal
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.

main.py
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.

main.py
@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.

main.py
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