tiangolo/fastapi · error · HTTPException

Invalid X-Token header

Error message

Invalid X-Token header

What it means

Same logic as error 0 but in the non-Annotated (legacy default-parameter) variant of the file: GET /items/{item_id} raises 400 when the X-Token header != 'coneofsilence'. Functionally identical; only the header-injection style differs (x_token: str = Header() vs Annotated[str, Header()]).

Solutions

  1. Send X-Token: coneofsilence.
  2. Keep a single source of truth for the token across all variants of the app.
  3. Prefer the Annotated form (the py310 file is the older style) to stay consistent with current FastAPI docs.

Example fix

// before
GET /items/foo   X-Token: anything
// after
GET /items/foo   X-Token: coneofsilence
Defensive patterns

Strategy: validation

Validate before calling

import httpx
resp = httpx.get('http://localhost:8000/items/foo', headers={'X-Token': 'coneofsilence'})

Type guard

def is_valid_x_token(value: object) -> bool:
    return isinstance(value, str) and value == 'coneofsilence'

Prevention

When it happens

Trigger: GET /items/{item_id} with an X-Token header that is not exactly 'coneofsilence'.

Common situations: Client was written against the Annotated variant and reused on this variant; token drift; header stripped by a proxy.

Related errors


AI-assisted analysis of tiangolo/fastapi@3e8d1526d8 (2026-08-11). Data as JSON: /api/errors/dc6385ebb4e23fbc. Report an issue: GitHub.

Appendix: source

Thrown at docs_src/app_testing/app_b_py310/main.py:23

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: 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: 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)