Skip to main content

Request Data and Parameters

In this tutorial, you will build a robust API endpoint that extracts and validates data from various parts of an HTTP request, including the path, query strings, headers, cookies, and the request body.

Prerequisites​

To follow this tutorial, ensure you have the following installed:

  • fastapi
  • pydantic
  • python-multipart (required for handling form data and file uploads)

Step 1: Define Path and Query Parameters​

Path parameters are part of the URL path, while query parameters appear after the ? in the URL. You can use Annotated with Path and Query to add validation and metadata.

Create a file named main.py:

from typing import Annotated
from fastapi import FastAPI, Path, Query

app = FastAPI()

@app.get("/items/{item_id}")
async def read_items(
item_id: Annotated[int, Path(title="The ID of the item to get", ge=1)],
q: Annotated[str | None, Query(alias="item-query", max_length=50)] = None,
):
results = {"item_id": item_id}
if q:
results.update({"q": q})
return results

In this example:

  • item_id is a Path parameter. It is always required. We added a numeric validation ge=1 (greater than or equal to 1) and a title for documentation.
  • q is a Query parameter. It is optional (defaults to None). We used an alias so the client sends item-query instead of q in the URL.

Step 2: Handle JSON Body Parameters​

For complex data, you use Pydantic models. If you have multiple models or extra fields in the body, you use the Body parameter.

Update your main.py:

from fastapi import Body
from pydantic import BaseModel

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

@app.put("/items/{item_id}")
async def update_item(
item_id: int,
item: Item,
user: User,
importance: Annotated[int, Body()]
):
results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
return results

When you declare multiple body parameters:

  • FastAPI expects a JSON body where each parameter name is a key. For example: {"item": {...}, "user": {...}, "importance": 5}.
  • If you want a single Pydantic model to be the entire body but still wrapped in a key, use Body(embed=True).

Step 3: Extract Headers and Cookies​

FastAPI allows you to extract metadata from the request using Header and Cookie.

from fastapi import Cookie, Header

@app.get("/info/")
async def read_info(
user_agent: Annotated[str | None, Header()] = None,
ads_id: Annotated[str | None, Cookie()] = None,
):
return {
"User-Agent": user_agent,
"ads_id": ads_id
}
  • Headers: By default, Header converts underscores to hyphens. A parameter named user_agent will automatically look for the User-Agent HTTP header.
  • Cookies: Cookie works exactly like Query and Path but looks for values in the request cookies.

Step 4: Upload Files and Form Data​

To receive form fields instead of JSON, use Form. To receive uploaded files, use File and UploadFile.

from fastapi import File, Form, UploadFile

@app.post("/files/")
async def create_file(
file: Annotated[bytes, File()],
fileb: Annotated[UploadFile, File()],
token: Annotated[str, Form()],
):
return {
"file_size": len(file),
"token": token,
"fileb_content_type": fileb.content_type,
"filename": fileb.filename,
}
  • bytes: If you declare the file parameter as bytes, FastAPI will read the entire file into memory. This is suitable for small files.
  • UploadFile: This is preferred for large files. It uses a "spooled" file (stored in memory up to a limit, then on disk) and provides an async interface (read(), write(), seek(), close()).
  • Form: Declares a field to be extracted from application/x-www-form-urlencoded or multipart/form-data.

Complete Result​

Your final API can now handle a wide variety of request data types with built-in validation and automatic documentation.

from typing import Annotated
from fastapi import FastAPI, Path, Query, Body, Cookie, Header, File, Form, UploadFile
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
name: str
price: float

@app.post("/complete/{item_id}")
async def complete_request(
item_id: Annotated[int, Path(ge=1)],
q: Annotated[str | None, Query()] = None,
item: Annotated[Item, Body(embed=True)],
user_agent: Annotated[str | None, Header()] = None,
session_id: Annotated[str | None, Cookie()] = None,
token: Annotated[str, Form()],
upload_file: Annotated[UploadFile, File()],
):
return {
"item_id": item_id,
"query": q,
"item": item,
"user_agent": user_agent,
"session_id": session_id,
"token": token,
"filename": upload_file.filename,
}

By combining these parameters, you can precisely define the interface of your API. FastAPI handles the extraction, validation, and generation of the OpenAPI schema (visible at /docs) automatically.