Path Operation Advanced Configuration¶
OpenAPI operationId¶
Warning
If you are not an "expert" in OpenAPI, you probably don't need this.
You can set the OpenAPIoperationId to be used in yourpath operation with the parameteroperation_id.
You would have to make sure that it is unique for each operation.
fromfastapiimportFastAPIapp=FastAPI()@app.get("/items/",operation_id="some_specific_id_you_define")asyncdefread_items():return[{"item_id":"Foo"}]Using thepath operation function name as the operationId¶
If you want to use your APIs' function names asoperationIds, you can iterate over all of them and override eachpath operation'soperation_id using theirAPIRoute.name.
You should do it after adding all yourpath operations.
fromfastapiimportFastAPIfromfastapi.routingimportAPIRouteapp=FastAPI()@app.get("/items/")asyncdefread_items():return[{"item_id":"Foo"}]defuse_route_names_as_operation_ids(app:FastAPI)->None:""" Simplify operation IDs so that generated API clients have simpler function names. Should be called only after all routes have been added. """forrouteinapp.routes:ifisinstance(route,APIRoute):route.operation_id=route.name# in this case, 'read_items'use_route_names_as_operation_ids(app)Tip
If you manually callapp.openapi(), you should update theoperationIds before that.
Warning
If you do this, you have to make sure each one of yourpath operation functions has a unique name.
Even if they are in different modules (Python files).
Exclude from OpenAPI¶
To exclude apath operation from the generated OpenAPI schema (and thus, from the automatic documentation systems), use the parameterinclude_in_schema and set it toFalse:
fromfastapiimportFastAPIapp=FastAPI()@app.get("/items/",include_in_schema=False)asyncdefread_items():return[{"item_id":"Foo"}]Advanced description from docstring¶
You can limit the lines used from the docstring of apath operation function for OpenAPI.
Adding an\f (an escaped "form feed" character) causesFastAPI to truncate the output used for OpenAPI at this point.
It won't show up in the documentation, but other tools (such as Sphinx) will be able to use the rest.
fromfastapiimportFastAPIfrompydanticimportBaseModelapp=FastAPI()classItem(BaseModel):name:strdescription:str|None=Noneprice:floattax:float|None=Nonetags:set[str]=set()@app.post("/items/",summary="Create an item")asyncdefcreate_item(item:Item)->Item:""" Create an item with all the information: - **name**: each item must have a name - **description**: a long description - **price**: required - **tax**: if the item doesn't have tax, you can omit this - **tags**: a set of unique tag strings for this item \f :param item: User input. """returnitemAdditional Responses¶
You probably have seen how to declare theresponse_model andstatus_code for apath operation.
That defines the metadata about the main response of apath operation.
You can also declare additional responses with their models, status codes, etc.
There's a whole chapter here in the documentation about it, you can read it atAdditional Responses in OpenAPI.
OpenAPI Extra¶
When you declare apath operation in your application,FastAPI automatically generates the relevant metadata about thatpath operation to be included in the OpenAPI schema.
Technical details
In the OpenAPI specification it is called theOperation Object.
It has all the information about thepath operation and is used to generate the automatic documentation.
It includes thetags,parameters,requestBody,responses, etc.
Thispath operation-specific OpenAPI schema is normally generated automatically byFastAPI, but you can also extend it.
Tip
This is a low level extension point.
If you only need to declare additional responses, a more convenient way to do it is withAdditional Responses in OpenAPI.
You can extend the OpenAPI schema for apath operation using the parameteropenapi_extra.
OpenAPI Extensions¶
Thisopenapi_extra can be helpful, for example, to declareOpenAPI Extensions:
fromfastapiimportFastAPIapp=FastAPI()@app.get("/items/",openapi_extra={"x-aperture-labs-portal":"blue"})asyncdefread_items():return[{"item_id":"portal-gun"}]If you open the automatic API docs, your extension will show up at the bottom of the specificpath operation.

And if you see the resulting OpenAPI (at/openapi.json in your API), you will see your extension as part of the specificpath operation too:
{"openapi":"3.1.0","info":{"title":"FastAPI","version":"0.1.0"},"paths":{"/items/":{"get":{"summary":"Read Items","operationId":"read_items_items__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"x-aperture-labs-portal":"blue"}}}}Custom OpenAPIpath operation schema¶
The dictionary inopenapi_extra will be deeply merged with the automatically generated OpenAPI schema for thepath operation.
So, you could add additional data to the automatically generated schema.
For example, you could decide to read and validate the request with your own code, without using the automatic features of FastAPI with Pydantic, but you could still want to define the request in the OpenAPI schema.
You could do that withopenapi_extra:
fromfastapiimportFastAPI,Requestapp=FastAPI()defmagic_data_reader(raw_body:bytes):return{"size":len(raw_body),"content":{"name":"Maaaagic","price":42,"description":"Just kiddin', no magic here. ✨",},}@app.post("/items/",openapi_extra={"requestBody":{"content":{"application/json":{"schema":{"required":["name","price"],"type":"object","properties":{"name":{"type":"string"},"price":{"type":"number"},"description":{"type":"string"},},}}},"required":True,},},)asyncdefcreate_item(request:Request):raw_body=awaitrequest.body()data=magic_data_reader(raw_body)returndataIn this example, we didn't declare any Pydantic model. In fact, the request body is not evenparsed as JSON, it is read directly asbytes, and the functionmagic_data_reader() would be in charge of parsing it in some way.
Nevertheless, we can declare the expected schema for the request body.
Custom OpenAPI content type¶
Using this same trick, you could use a Pydantic model to define the JSON Schema that is then included in the custom OpenAPI schema section for thepath operation.
And you could do this even if the data type in the request is not JSON.
For example, in this application we don't use FastAPI's integrated functionality to extract the JSON Schema from Pydantic models nor the automatic validation for JSON. In fact, we are declaring the request content type as YAML, not JSON:
importyamlfromfastapiimportFastAPI,HTTPException,RequestfrompydanticimportBaseModel,ValidationErrorapp=FastAPI()classItem(BaseModel):name:strtags:list[str]@app.post("/items/",openapi_extra={"requestBody":{"content":{"application/x-yaml":{"schema":Item.model_json_schema()}},"required":True,},},)asyncdefcreate_item(request:Request):raw_body=awaitrequest.body()try:data=yaml.safe_load(raw_body)exceptyaml.YAMLError:raiseHTTPException(status_code=422,detail="Invalid YAML")try:item=Item.model_validate(data)exceptValidationErrorase:raiseHTTPException(status_code=422,detail=e.errors(include_url=False))returnitemNevertheless, although we are not using the default integrated functionality, we are still using a Pydantic model to manually generate the JSON Schema for the data that we want to receive in YAML.
Then we use the request directly, and extract the body asbytes. This means that FastAPI won't even try to parse the request payload as JSON.
And then in our code, we parse that YAML content directly, and then we are again using the same Pydantic model to validate the YAML content:
importyamlfromfastapiimportFastAPI,HTTPException,RequestfrompydanticimportBaseModel,ValidationErrorapp=FastAPI()classItem(BaseModel):name:strtags:list[str]@app.post("/items/",openapi_extra={"requestBody":{"content":{"application/x-yaml":{"schema":Item.model_json_schema()}},"required":True,},},)asyncdefcreate_item(request:Request):raw_body=awaitrequest.body()try:data=yaml.safe_load(raw_body)exceptyaml.YAMLError:raiseHTTPException(status_code=422,detail="Invalid YAML")try:item=Item.model_validate(data)exceptValidationErrorase:raiseHTTPException(status_code=422,detail=e.errors(include_url=False))returnitemTip
Here we reuse the same Pydantic model.
But the same way, we could have validated it in some other way.







