Request body¶
FastAPI decodes a JSON request body and validates it against one or several Pydantic models (Body(embed=)
wraps a single model in a key). The binary decodes the body as CPython's json module does, then validates it
like Pydantic, with the same 422 errors.
A JSON body read into one model, into several (item and user become keys of the body), with a
singular value (Body(gt=0)) and with Body(embed=True). Like every example on this site, it is compiled and compared with FastAPI in CI (how).
from typing import Annotated
from fastapi import APIRouter, Body
from pydantic import BaseModel
router = APIRouter(prefix="/body", tags=["body"])
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
class User(BaseModel):
username: str
full_name: str | None = None
@router.post("/items")
async def create_item(item: Item):
data = item.model_dump()
if item.tax is not None:
data["price_with_tax"] = item.price + item.tax
return data
@router.put("/items/{item_id}")
async def update_item(
item_id: int,
item: Item,
user: User,
importance: Annotated[int, Body(gt=0)],
q: str | None = None,
):
result = {"item_id": item_id, "item": item, "user": user, "importance": importance}
if q:
result["q"] = q
return result
@router.put("/embedded/{item_id}")
async def update_embedded(item_id: int, item: Annotated[Item, Body(embed=True)]):
return {"item_id": item_id, "item": item}
Invalid JSON, a trailing comma, a missing body or a wrong type give FastAPI's exact error bodies.
What is native¶
- JSON bodies (one or several models,
Body(embed=)). - JSON bodies (and
json.loads) are decoded as CPython'sjsondoes:NaN/Infinity, encodings detected from the bytes (BOM, UTF-16/32), the 422json_invalidwith CPython'sctx.errorand position in code points (the trailing-comma messages of 3.13+), undecodable bytes as FastAPI's 400, NaN/inf in a 422's rawinputas Starlette's 500. A\uXXXXescape ending the text follows the newest CPython patches (unterminated string; before 3.13.13/3.14.4 or so, and on 3.12, "Invalid \uXXXX escape"):PY2AXUM_PYTHON_VERSION=3.14.0selects the older message. - A JSON
nullbody is no body (missing, or the parameter's default). A body withoutContent-Typeis not decoded as JSON from FastAPI 0.132 on (strict_content_type, its default). FastAPI(strict_content_type=True | False)(a literal): without it, the default of the locked FastAPI.
How the models themselves validate is described in Models (Pydantic). Request size limits (none by default, like uvicorn) are in Security § Request input.
What stays in Python¶
Nothing specific: a body model is refused only for what its fields or validators use (Models).
Differences¶
Known differences
A lone surrogate escape ("\ud83d") becomes U+FFFD;
an integer beyond 64 bits raises OverflowError (500); nesting deeper than 10 000 levels is FastAPI's 400
(CPython's limit depends on its C stack, and FastAPI may answer 500 when it encodes a deeply nested input).