Request Body and Pydantic Models

To receive structured data (typically JSON) in a POST, PUT or PATCH request, you declare a parameter with a Pydantic model type. FastAPI reads the body, validates it and gives you a typed object.

Defining a Model

from pydantic import BaseModel, Field, EmailStr

class Item(BaseModel):
    name: str = Field(min_length=1, max_length=50, examples=["Keyboard"])
    price: float = Field(gt=0, description="Price in USD")
    is_offer: bool | None = None
    owner_email: EmailStr | None = None

@app.post("/items/")
def create_item(item: Item):
    return {"item_name": item.name, "price_with_tax": item.price * 1.12}

Combining Body, Path and Query Parameters

@app.put("/items/{item_id}")
def update_item(item_id: int, item: Item, q: str | None = None):
    return {"item_id": item_id, **item.model_dump(), "q": q}

FastAPI routes each parameter to its source: item_id from the path, item from the JSON body, q from the query string.

Nested Models

class Tag(BaseModel):
    name: str

class Article(BaseModel):
    title: str
    tags: list[Tag] = []
    metadata_: dict[str, str] | None = None

@app.post("/articles/")
def create_article(article: Article):
    return {"title": article.title, "first_tag": article.tags[0].name if article.tags else None}

Arbitrarily deep nesting works: lists of models, dicts, optional fields. Validation errors point to the exact nested path.

Multiple Body Parameters and Embedded Bodies

from fastapi import Body

@app.put("/items/{item_id}")
def update(item_id: int, item: Item, user: User):
    return {"item_id": item_id, "item": item.model_dump(), "user": user.model_dump()}

Two models in one function means the JSON body must be {"item": {...}, "user": {...}} (FastAPI embeds them automatically when there is more than one body parameter). With a single model you can force embedding using item: Item = Body(embed=True).

Quick Questions and Answers

Q: What is the difference between .model_dump() and .model_dump_json()?

A: model_dump() returns a Python dict; model_dump_json() returns a JSON string directly (faster, no intermediate dict). In Pydantic v1 these were called .dict() and .json().

Q: Does FastAPI validate the response body too?

A: Only if you declare a response_model. Without it, whatever the function returns is serialized as-is. With it, the output is filtered and validated against the model (see the next page).

Q: What happens with extra/unknown fields in the request body?

A: By default they are ignored. You can change this with model_config = ConfigDict(extra="forbid") to reject unknown fields with a 422 error, or extra="allow" to keep them.

Conclusion

Pydantic models are the contract of your API: declare once and you get validation, conversion, nested structures, editor autocompletion and schema generation. This single mechanism replaces a lot of manual parsing code.