FastAPI Interactive Docs

Header Parameters

Last time, in Cookie Parameters, we read a session cookie off the request. Headers are cookies' close cousin. They carry things like User-Agent, content types, and auth tokens. Reading them uses the exact same pattern, with one small naming quirk we teased. Let's clear it up. 🤔

Reading a header

Import Header from fastapi and use it inside Annotated, same as Cookie and Query.

main.py
from typing import Annotated, Optionalfrom fastapi import FastAPI, Headerapp = FastAPI()@app.get("/blog")async def read_blog(user_agent: Annotated[Optional[str], Header()] = None):    return {"user_agent": user_agent}

Let's review:

  1. Header() tells FastAPI to pull the value from the request headers, not the query string.

  2. The type is Optional[str] with a default of None, so the header is optional.

  3. Without Header(), FastAPI would treat user_agent as a query parameter, the same trap we saw with cookies.

The underscore quirk

Here is the quirk. HTTP headers use hyphens, like User-Agent, but Python variables cannot have hyphens. So FastAPI automatically converts underscores in your parameter name to hyphens when matching the header.

So your Python user_agent reads the real User-Agent header. Headers are also case-insensitive, so you write plain snake_case and FastAPI handles the rest. No manual mapping needed.

Repeated headers

A header can appear more than once in a request. To collect every value, type it as a list.

@app.get("/blog")async def read_blog(x_token: Annotated[Optional[list[str]], Header()] = None):    return {"x_token": x_token}

Send X-Token: foo and X-Token: bar and you get back {"x_token": ["foo", "bar"]}. FastAPI gathers both into a list.

Gotcha: that underscore-to-hyphen conversion is on by default, which is what you want almost always. You can switch it off with Header(convert_underscores=False), but only do that for an oddball header, since many proxies and servers reject headers that actually contain underscores. When in doubt, leave it on. 😁