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

  1. Send the header exactly: X-Token: coneofsilence on every GET /items/{item_id} request.
  2. If the header is present but wrong, confirm there is no trailing whitespace or quotes added by the HTTP client.
  3. Move the secret out of source into an environment variable and load it on both server and client sides from the same source.
  4. 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

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)