tiangolo/fastapi · error · HTTPException
X-Key header invalid
Error message
X-Key header invalid
What it means
FastAPI returns HTTP 400 with detail "X-Key header invalid" when the `X-Key` request header does not equal the expected "fake-super-secret-key". The check runs in `verify_key`, the second application-wide dependency registered on the FastAPI app, executed after verify_token for every route. Unlike verify_token, verify_key returns x_key on success, demonstrating that global dependencies may also yield values.
Source
Thrown at docs_src/dependencies/tutorial012_an_py310.py:13
from typing import Annotated
from fastapi import Depends, FastAPI, Header, HTTPException
async def verify_token(x_token: Annotated[str, Header()]):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")
async def verify_key(x_key: Annotated[str, Header()]):
if x_key != "fake-super-secret-key":
raise HTTPException(status_code=400, detail="X-Key header invalid")
return x_key
app = FastAPI(dependencies=[Depends(verify_token), Depends(verify_key)])
@app.get("/items/")
async def read_items():
return [{"item": "Portal Gun"}, {"item": "Plumbus"}]
@app.get("/users/")
async def read_users():
return [{"username": "Rick"}, {"username": "Morty"}]
View on GitHub (pinned to 42a41db11f)
Solutions
- Send both `X-Token: fake-super-secret-token` and `X-Key: fake-super-secret-key`.
- Move shared secrets to configuration/env so both can be rotated together.
- Return 401 for auth-key failures instead of 400.
- Add a client helper that injects both headers automatically.
Example fix
# before
if x_key != "fake-super-secret-key":
raise HTTPException(status_code=400, detail="X-Key header invalid")
# after
import os
if x_key != os.environ["EXPECTED_KEY"]:
raise HTTPException(status_code=401, detail="Invalid X-Key") Defensive patterns
Strategy: validation
Validate before calling
headers = {"X-Token": TOKEN, "X-Key": KEY}
missing = [h for h in ("X-Token", "X-Key") if not headers.get(h)]
if missing:
raise ValueError(f"missing headers: {missing}") Type guard
def headers_complete(headers: dict) -> bool:
return bool(headers.get("X-Token")) and bool(headers.get("X-Key")) Try / catch
r = client.get("/users/", headers=headers)
if r.status_code == 400 and "X-Key header invalid" in r.json().get("detail", ""):
# rotate/refresh the key
... Prevention
- Send both headers from one shared client wrapper.
- Keep token/key pairs consistent after rotation.
- Fail fast client-side on missing headers.
When it happens
Trigger: Any request to /items/ or /users/ that passes X-Token verification but sends no `X-Key` header or a wrong value.
Common situations: Clients passing the first key but not the second; mismatched key pairs after rotation; header stripped by an intermediary; ordering confusion about which key is which.
Related errors
- X-Token header invalid
- X-Token header invalid
- X-Key header invalid
- Invalid X-Token header
- Invalid X-Token header
AI-assisted analysis of tiangolo/fastapi@42a41db11f (2026-08-04).
Data as JSON: /data/errors/081f977b85d7c8fa.json.
Report an issue: GitHub.