Operations and Path Items
FastAPI automatically generates an OpenAPI schema for your application by inspecting your route decorators, function signatures, and return types. This process involves mapping FastAPI's internal routing structures to the OpenAPI specification's models, specifically Path Items and Operations.
Mapping Routes to Path Items
In OpenAPI, a Path Item represents a single relative path (e.g., /items/{item_id}). A single PathItem can contain multiple Operations, such as GET, POST, or DELETE.
When you define a route in FastAPI:
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
def read_item(item_id: int):
return {"item_id": item_id}
FastAPI's get_openapi_path function (in fastapi/openapi/utils.py) creates a PathItem for the path /items/{item_id}. Internally, the PathItem class (from fastapi.openapi.models) holds these operations in fields named after the HTTP methods:
# Internal representation in fastapi.openapi.models.PathItem
class PathItem(BaseModelWithConfig):
get: Operation | None = None
put: Operation | None = None
post: Operation | None = None
# ... other methods
Operations and Metadata
Each HTTP method on a path is represented by an Operation object. FastAPI populates the Operation metadata using information from your path operation decorator and function.
Summary and Description
FastAPI generates a summary from your function name (converting underscores to spaces and title-casing it) and uses the function's docstring or the description parameter for the description field.
@app.get("/items/", summary="List all items", description="Retrieve a list of items from the database")
def read_items():
return []
Internally, get_openapi_operation_metadata handles this mapping:
# From fastapi/openapi/utils.py
operation["summary"] = generate_operation_summary(route=route, method=method)
if route.description:
operation["description"] = route.description
Operation IDs
The operationId is a unique identifier for the operation. FastAPI generates this automatically using your function name and path, but you can override it. If duplicate IDs are detected, FastAPI issues a warning during schema generation.
@app.get("/items/", operation_id="custom_list_items")
def read_items():
return []
Request Bodies and Media Types
When a route expects a body (e.g., via a Pydantic model or Body() parameter), FastAPI constructs a RequestBody object.
RequestBody and MediaType
The RequestBody contains a content dictionary where keys are media types (like application/json) and values are MediaType objects.
from pydantic import BaseModel
from fastapi import Body
class Item(BaseModel):
name: str
price: float
@app.post("/items/")
def create_item(item: Item):
return item
FastAPI uses get_openapi_operation_request_body to build this structure. The MediaType object includes the JSON Schema for the body:
# Internal representation in fastapi.openapi.models.MediaType
class MediaType(BaseModelWithConfig):
schema_: Schema | Reference | None = Field(default=None, alias="schema")
example: Any | None = None
examples: dict[str, Example | Reference] | None = None
encoding: dict[str, Encoding] | None = None
Complex Encodings
For multipart/form-data or application/x-www-form-urlencoded requests, FastAPI uses the Encoding class to define how individual fields should be serialized. This is common when uploading files alongside form data.
Responses and Status Codes
FastAPI automatically generates a default Response for your route's success status code (usually 200 or the value in status_code).
Default and Additional Responses
You can define additional responses using the responses parameter. FastAPI merges these into the Operation.responses dictionary.
@app.get("/items/{item_id}", responses={404: {"description": "Item not found"}})
def read_item(item_id: str):
if item_id == "portal-gun":
return {"item_id": item_id}
return JSONResponse(status_code=404, content={"message": "Not found"})
If your route has parameters or a body, FastAPI automatically adds a 422 Unprocessable Entity response to the OpenAPI schema to describe validation errors, unless you provide your own 422 or 4XX response.
Advanced Customization with openapi_extra
If you need to add fields to the OpenAPI schema that FastAPI doesn't support natively (like specification extensions starting with x-), use the openapi_extra parameter in your path decorator.
@app.get("/", openapi_extra={"x-custom-extension": "internal-use-only"})
def root():
return {"message": "Hello World"}
FastAPI uses deep_dict_update to merge your openapi_extra dictionary directly into the generated Operation object:
# From fastapi/openapi/utils.py
if route.openapi_extra:
deep_dict_update(operation, route.openapi_extra)
This allows you to override any part of the generated operation or add custom metadata required by third-party tools.