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.
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:
Header()tells FastAPI to pull the value from the request headers, not the query string.The type is
Optional[str]with a default ofNone, so the header is optional.Without
Header(), FastAPI would treatuser_agentas 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. 😁
