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.