Skip to main content
The response_model parameter allows you to define the structure of your API responses, providing automatic validation, serialization, and documentation.

Basic Response Model

Use response_model to declare what your endpoint returns:
from typing import Any
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None
    tags: list[str] = []

@app.post("/items/", response_model=Item)
async def create_item(item: Item) -> Any:
    return item

@app.get("/items/", response_model=list[Item])
async def read_items() -> Any:
    return [
        {"name": "Portal Gun", "price": 42.0},
        {"name": "Plumbus", "price": 32.0},
    ]
FastAPI uses the response_model to:
  • Convert the output data to the declared type
  • Validate the data
  • Add a JSON Schema for the response in the OpenAPI docs
  • Limit the output data to what’s in the model

Return Type Annotations

You can also use return type annotations (Python 3.9+):
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None

@app.post("/items/")
async def create_item(item: Item) -> Item:
    return item

@app.get("/items/")
async def read_items() -> list[Item]:
    return [
        Item(name="Portal Gun", price=42.0),
        Item(name="Plumbus", price=32.0),
    ]
Using return type annotations is the modern, recommended approach. It provides better type checking in your editor.

Response Model vs Return Type

@app.get("/items/")
async def read_items() -> list[Item]:
    return [Item(name="Foo", price=42.0)]
# Clean, modern syntax

Filtering Response Data

One of the most powerful features is automatic filtering of response data:
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()

class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    full_name: str | None = None

class UserOut(BaseModel):
    username: str
    email: EmailStr
    full_name: str | None = None

class UserInDB(BaseModel):
    username: str
    hashed_password: str
    email: EmailStr
    full_name: str | None = None

def fake_password_hasher(raw_password: str):
    return "supersecret" + raw_password

def fake_save_user(user_in: UserIn):
    hashed_password = fake_password_hasher(user_in.password)
    user_in_db = UserInDB(**user_in.model_dump(), hashed_password=hashed_password)
    print("User saved! ..not really")
    return user_in_db

@app.post("/user/", response_model=UserOut)
async def create_user(user_in: UserIn):
    user_saved = fake_save_user(user_in)
    return user_saved
Even though user_saved contains hashed_password, only the fields in UserOut are returned to the client. The password is automatically filtered out.

Response Model with Default Values

You can exclude fields with default values from the response:
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float = 10.5
    tags: list[str] = []

@app.get(
    "/items/{item_id}",
    response_model=Item,
    response_model_exclude_unset=True
)
async def read_item(item_id: str):
    items = {
        "foo": {"name": "Foo", "price": 50.2},
        "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
    }
    return items[item_id]
1

response_model_exclude_unset=True

Don’t include fields that weren’t explicitly set
2

response_model_exclude_defaults=True

Don’t include fields with their default values
3

response_model_exclude_none=True

Don’t include fields with None values

Include and Exclude Fields

You can explicitly include or exclude specific fields:
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None

@app.get(
    "/items/{item_id}/name",
    response_model=Item,
    response_model_include={"name", "description"}
)
async def read_item_name(item_id: str):
    return {"name": "Foo", "description": "A foo item", "price": 42.0}

@app.get(
    "/items/{item_id}/public",
    response_model=Item,
    response_model_exclude={"tax"}
)
async def read_item_public(item_id: str):
    return {"name": "Bar", "description": "A bar item", "price": 62.0, "tax": 12.4}
Use sets (not lists) for response_model_include and response_model_exclude.

Multiple Models Pattern

Create separate models for input and output:
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()

# Base model with common fields
class UserBase(BaseModel):
    username: str
    email: EmailStr
    full_name: str | None = None

# Input model includes password
class UserCreate(UserBase):
    password: str

# Output model excludes password
class UserResponse(UserBase):
    id: int

# Database model includes hashed password
class UserInDB(UserBase):
    id: int
    hashed_password: str

@app.post("/users/", response_model=UserResponse)
async def create_user(user: UserCreate):
    # Hash password and save to database
    user_in_db = UserInDB(
        id=1,
        **user.model_dump(exclude={"password"}),
        hashed_password="hashed_" + user.password
    )
    return user_in_db

Union Response Types

Return different model types based on conditions:
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class ItemBase(BaseModel):
    description: str
    type: str

class CarItem(ItemBase):
    type: str = "car"

class PlaneItem(ItemBase):
    type: str = "plane"
    size: int

@app.get("/items/{item_id}", response_model=PlaneItem | CarItem)
async def read_item(item_id: str):
    items = {
        "item1": {"description": "All my friends drive a low rider", "type": "car"},
        "item2": {
            "description": "Music is my aeroplane",
            "type": "plane",
            "size": 5,
        },
    }
    return items[item_id]
The response will match whichever model is appropriate based on the data structure.

Response Model Benefits

1

Security

Automatically filters sensitive data like passwords
2

Documentation

Generates accurate OpenAPI schema for responses
3

Validation

Validates outgoing data, catching bugs early
4

Serialization

Converts complex types to JSON-compatible formats
5

Type Safety

Provides editor autocomplete and type checking

Disabling Response Model

In rare cases, you can disable response validation:
from fastapi import FastAPI, Response

app = FastAPI()

@app.get("/portal", response_model=None)
async def get_portal(teleport: bool = False) -> Response | dict:
    if teleport:
        return Response(status_code=307, headers={"Location": "https://example.com"})
    return {"message": "Here's your interdimensional portal."}
Only disable response models when absolutely necessary. You lose validation and documentation benefits.

Build docs developers (and LLMs) love