HTTP headers contain metadata about requests and responses. FastAPI makes it easy to read and validate headers using the Header() function.
Use Header() to declare header parameters:
from fastapi import FastAPI, Header
app = FastAPI()
@app.get("/items/")
async def read_items(user_agent: str | None = Header(default=None)):
return {"User-Agent": user_agent}
Header() works like Query() and Cookie(), but reads values from HTTP headers.
Automatic Underscore to Hyphen Conversion
HTTP headers use hyphens (e.g., User-Agent), but Python variables can’t contain hyphens. FastAPI automatically converts underscores to hyphens:
from fastapi import FastAPI, Header
app = FastAPI()
@app.get("/items/")
async def read_items(
user_agent: str | None = Header(default=None),
content_type: str | None = Header(default=None),
):
return {
"User-Agent": user_agent,
"Content-Type": content_type
}
user_agent parameter automatically matches the User-Agent header.
Disable Automatic Conversion
If you need to preserve underscores, disable conversion:
from fastapi import FastAPI, Header
app = FastAPI()
@app.get("/items/")
async def read_items(
strange_header: str | None = Header(default=None, convert_underscores=False),
):
return {"strange_header": strange_header}
Now it will only match a header literally named strange_header, not Strange-Header.
Make headers required by not providing a default:
from fastapi import FastAPI, Header, HTTPException, status
app = FastAPI()
@app.get("/items/")
async def read_items(authorization: str = Header()):
if not authorization.startswith("Bearer "):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authorization header"
)
return {"token": authorization}
Requests without the required header will return a 422 validation error.
Make headers optional with a default value:
from fastapi import FastAPI, Header
app = FastAPI()
@app.get("/items/")
async def read_items(
user_agent: str | None = Header(default=None),
accept_language: str | None = Header(default=None),
x_request_id: str | None = Header(default=None),
):
return {
"User-Agent": user_agent,
"Accept-Language": accept_language,
"X-Request-ID": x_request_id
}
Header() supports validation parameters:
from fastapi import FastAPI, Header
app = FastAPI()
@app.get("/items/")
async def read_items(
x_token: str = Header(
min_length=32,
max_length=64,
description="API token header"
),
x_request_id: str = Header(
pattern=r"^[a-f0-9-]{36}$",
description="UUID request ID"
),
):
return {
"X-Token": x_token,
"X-Request-ID": x_request_id
}
String Validation
Use min_length, max_length, pattern for string headers
Documentation
Add title, description, examples for better API docs
Type Conversion
Declare types like int, bool for automatic conversion
Some headers can appear multiple times. Receive them as a list:
from fastapi import FastAPI, Header
app = FastAPI()
@app.get("/items/")
async def read_items(x_token: list[str] | None = Header(default=None)):
return {"X-Token values": x_token}
For a request with:
X-Token: token1
X-Token: token2
X-Token: token3
You’ll receive:
x_token = ["token1", "token2", "token3"]
Common Standard Headers
from fastapi import FastAPI, Header, HTTPException, status
app = FastAPI()
@app.get("/protected")
async def protected_route(authorization: str = Header()):
if not authorization.startswith("Bearer "):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authorization"
)
token = authorization.replace("Bearer ", "")
# Validate token...
return {"message": "Access granted"}
from fastapi import FastAPI, Header
app = FastAPI()
@app.post("/data")
async def receive_data(
data: bytes,
content_type: str = Header(),
):
if content_type != "application/octet-stream":
raise HTTPException(
status_code=415,
detail="Unsupported media type"
)
return {"received": len(data)}
from fastapi import FastAPI, Header
from fastapi.responses import JSONResponse, PlainTextResponse
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(
item_id: int,
accept: str = Header(default="application/json"),
):
item = {"item_id": item_id, "name": "Foo"}
if "text/plain" in accept:
return PlainTextResponse(f"Item {item_id}: {item['name']}")
return JSONResponse(item)
from fastapi import FastAPI, Header
app = FastAPI()
@app.get("/")
async def root(user_agent: str | None = Header(default=None)):
is_mobile = user_agent and "Mobile" in user_agent
return {
"user_agent": user_agent,
"is_mobile": is_mobile
}
Read custom application headers (usually prefixed with X-):
from fastapi import FastAPI, Header
app = FastAPI()
@app.get("/items/")
async def read_items(
x_api_key: str = Header(),
x_request_id: str | None = Header(default=None),
x_correlation_id: str | None = Header(default=None),
):
return {
"api_key": x_api_key,
"request_id": x_request_id,
"correlation_id": x_correlation_id
}
Custom headers conventionally use the X- prefix, though this is no longer required by RFC 6648.
Use aliases for headers with special naming:
from fastapi import FastAPI, Header
app = FastAPI()
@app.get("/items/")
async def read_items(
api_key: str = Header(alias="X-API-Key"),
request_id: str | None = Header(default=None, alias="X-Request-ID"),
):
return {
"api_key": api_key,
"request_id": request_id
}
Case Sensitivity
HTTP header names are case-insensitive. FastAPI handles this automatically:
@app.get("/items/")
async def read_items(user_agent: str | None = Header(default=None)):
return {"User-Agent": user_agent}
All of these match:
User-Agent: Mozilla/5.0
user-agent: Mozilla/5.0
USER-AGENT: Mozilla/5.0
@app.get("/items/")
async def read_items(x_token: str = Header()):
return {"token": x_token}
# Reads from: X-Token: abc123
Authentication Example
Common pattern for API key authentication:
from fastapi import FastAPI, Header, HTTPException, status
app = FastAPI()
API_KEYS = {"secret-key-123", "another-key-456"}
@app.get("/secure")
async def secure_endpoint(x_api_key: str = Header()):
if x_api_key not in API_KEYS:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid API Key"
)
return {"message": "Access granted"}
@app.get("/public")
async def public_endpoint():
return {"message": "No authentication required"}
While Header() reads headers, use Response to set them:
from fastapi import FastAPI, Response
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: int, response: Response):
response.headers["X-Item-ID"] = str(item_id)
response.headers["X-Process-Time"] = "0.123"
return {"item_id": item_id}
All available parameters:
- Validation:
min_length, max_length, pattern
- Documentation:
title, description, examples, deprecated
- Behavior:
alias, default, convert_underscores, include_in_schema
curl http://localhost:8000/items/ \
-H "X-Token: secret123" \
-H "X-Request-ID: 550e8400-e29b-41d4-a716-446655440000"
Common Use Cases
Authentication
API keys, bearer tokens, basic auth
Request Tracing
Request IDs, correlation IDs for logging
Content Negotiation
Accept, Content-Type headers
Client Information
User-Agent, Accept-Language for analytics
API Versioning
Custom version headers for API versioning