tiangolo/fastapi · error · HTTPException
Invalid X-Token header
Error message
Invalid X-Token header
What it means
This HTTPException (status 400) is raised by the GET /items/{item_id} handler when the request's X-Token header does not equal the hardcoded secret 'coneofsilence'. It is a hand-rolled authentication gate: FastAPI injects the header via Annotated[str, Header()], and a plain equality check rejects anything that does not match exactly. The status code 400 (rather than 401/403) is a docs-example choice, not a security best practice. It fires before the item lookup, so no resource access occurs without a valid token.
Solutions
- Send the header exactly: X-Token: coneofsilence on every GET /items/{item_id} request.
- If the header is present but wrong, confirm there is no trailing whitespace or quotes added by the HTTP client.
- Move the secret out of source into an environment variable and load it on both server and client sides from the same source.
- For production, replace this check with a real auth dependency (OAuth2/FastAPI Security) and use 401/403 instead of 400.
Example fix
// before curl -H 'X-Token: secret' http://localhost:8000/items/foo // after curl -H 'X-Token: coneofsilence' http://localhost:8000/items/foo
Defensive patterns
Strategy: validation
Validate before calling
import httpx
SECRET = 'coneofsilence'
def valid_token(token: str) -> bool:
return token == SECRET
# before the call:
headers = {'X-Token': SECRET} if valid_token(SECRET) else {}
resp = httpx.get('http://localhost:8000/items/foo', headers=headers) Type guard
def is_valid_x_token(value: object) -> bool:
return isinstance(value, str) and value == 'coneofsilence' Prevention
- Load the expected token from one shared config source on both server and client.
- Centralize header construction in a single client function so every call is consistent.
- Add an integration test asserting the header is sent on every protected route.
When it happens
Trigger: A GET request to /items/{item_id} with a missing, empty, misspelled, or wrong X-Token header. Examples: omitting the header entirely (FastAPI then returns 422 for missing required header before this line), sending X-Token: wrong, or sending X-Token: ConeOfSilence (case mismatch).
Common situations: Developers copy the tutorial token 'coneofsilence' into a test client and later rotate it in one place but not another; CI tests forget to set the header; a frontend proxy strips custom headers; or the header is sent with surrounding whitespace or different casing of the value.
Related errors
AI-assisted analysis of tiangolo/fastapi@3e8d1526d8 (2026-08-11).
Data as JSON: /api/errors/23ef81dae03fef0d.
Report an issue: GitHub.
Appendix: source
Thrown at docs_src/app_testing/app_b_an_py310/main.py:25
fake_db = {
"foo": {"id": "foo", "title": "Foo", "description": "There goes my hero"},
"bar": {"id": "bar", "title": "Bar", "description": "The bartenders"},
}
app = FastAPI()
class Item(BaseModel):
id: str
title: str
description: str | None = None
@app.get("/items/{item_id}", response_model=Item)
async def read_main(item_id: str, x_token: Annotated[str, Header()]):
if x_token != fake_secret_token:
raise HTTPException(status_code=400, detail="Invalid X-Token header")
if item_id not in fake_db:
raise HTTPException(status_code=404, detail="Item not found")
return fake_db[item_id]
@app.post("/items/")
async def create_item(item: Item, x_token: Annotated[str, Header()]) -> Item:
if x_token != fake_secret_token:
raise HTTPException(status_code=400, detail="Invalid X-Token header")
if item.id in fake_db:
raise HTTPException(status_code=409, detail="Item already exists")
fake_db[item.id] = item.model_dump()
return item
View on GitHub (pinned to 3e8d1526d8)