{"id":"081f977b85d7c8fa","repo":"tiangolo/fastapi","slug":"x-key-header-invalid-081f97","errorCode":null,"errorMessage":"X-Key header invalid","messagePattern":"X-Key header invalid","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"docs_src/dependencies/tutorial012_an_py310.py","lineNumber":13,"sourceCode":"from typing import Annotated\n\nfrom fastapi import Depends, FastAPI, Header, HTTPException\n\n\nasync def verify_token(x_token: Annotated[str, Header()]):\n    if x_token != \"fake-super-secret-token\":\n        raise HTTPException(status_code=400, detail=\"X-Token header invalid\")\n\n\nasync def verify_key(x_key: Annotated[str, Header()]):\n    if x_key != \"fake-super-secret-key\":\n        raise HTTPException(status_code=400, detail=\"X-Key header invalid\")\n    return x_key\n\n\napp = FastAPI(dependencies=[Depends(verify_token), Depends(verify_key)])\n\n\n@app.get(\"/items/\")\nasync def read_items():\n    return [{\"item\": \"Portal Gun\"}, {\"item\": \"Plumbus\"}]\n\n\n@app.get(\"/users/\")\nasync def read_users():\n    return [{\"username\": \"Rick\"}, {\"username\": \"Morty\"}]\n","sourceCodeStart":1,"sourceCodeEnd":28,"githubUrl":"https://github.com/tiangolo/fastapi/blob/42a41db11f6882807ac3c057b942178d53b97438/docs_src/dependencies/tutorial012_an_py310.py#L1-L28","documentation":"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.","triggerScenarios":"Any request to /items/ or /users/ that passes X-Token verification but sends no `X-Key` header or a wrong value.","commonSituations":"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.","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."],"exampleFix":"# before\nif x_key != \"fake-super-secret-key\":\n    raise HTTPException(status_code=400, detail=\"X-Key header invalid\")\n\n# after\nimport os\nif x_key != os.environ[\"EXPECTED_KEY\"]:\n    raise HTTPException(status_code=401, detail=\"Invalid X-Key\")","handlingStrategy":"validation","validationCode":"headers = {\"X-Token\": TOKEN, \"X-Key\": KEY}\nmissing = [h for h in (\"X-Token\", \"X-Key\") if not headers.get(h)]\nif missing:\n    raise ValueError(f\"missing headers: {missing}\")","typeGuard":"def headers_complete(headers: dict) -> bool:\n    return bool(headers.get(\"X-Token\")) and bool(headers.get(\"X-Key\"))","tryCatchPattern":"r = client.get(\"/users/\", headers=headers)\nif r.status_code == 400 and \"X-Key header invalid\" in r.json().get(\"detail\", \"\"):\n    # rotate/refresh the key\n    ...","preventionTips":["Send both headers from one shared client wrapper.","Keep token/key pairs consistent after rotation.","Fail fast client-side on missing headers."],"tags":["fastapi","authentication","headers","global-dependency","http-400"],"analyzedSha":"42a41db11f6882807ac3c057b942178d53b97438","analyzedAt":"2026-08-04T19:23:32.007Z","schemaVersion":2}