tiangolo/fastapi · error · HTTPException
You can only update the item: plumbus
Error message
You can only update the item: plumbus
What it means
Raised (403) by PUT /items/{item_id} when item_id is not exactly 'plumbus'. This is a hard-coded business rule in the demo: only the plumbus item is mutable. The route declares responses={403: {...}} documenting it. The X-Token dependency has already run, so this is an authorization/semantics error, not an auth error.
Solutions
- Only PUT /items/plumbus.
- If broader mutability is needed, remove the hard-coded check and implement per-item ownership rules.
- Document the writable set in the API contract so clients do not attempt others.
Example fix
// before PUT /items/gun // after PUT /items/plumbus
Defensive patterns
Strategy: validation
Validate before calling
import httpx
WRITABLE = {'plumbus'}
def update(item_id: str):
if item_id not in WRITABLE:
raise ValueError(f'{item_id} is not writable')
return httpx.put(f'http://localhost:8000/items/{item_id}', headers={'X-Token': 'fake-super-secret-token'}) Type guard
def is_writable_item(item_id: object) -> bool:
return isinstance(item_id, str) and item_id == 'plumbus' Prevention
- Maintain a client-side set of writable ids.
- Do not assume readability implies writability.
- Document the writable contract for API consumers.
When it happens
Trigger: PUT /items/gun or PUT /items/<anything-not-plumbus> with a valid token. Even though 'gun' exists for GET, it is not writable.
Common situations: Assuming all readable items are writable; automating PUTs across all known ids; copy-pasting a PUT template with a fixed id.
Related errors
AI-assisted analysis of tiangolo/fastapi@3e8d1526d8 (2026-08-11).
Data as JSON: /api/errors/21ab48034f47e139.
Report an issue: GitHub.
Appendix: source
Thrown at docs_src/bigger_applications/app_an_py310/routers/items.py:35
async def read_items():
return fake_items_db
@router.get("/{item_id}")
async def read_item(item_id: str):
if item_id not in fake_items_db:
raise HTTPException(status_code=404, detail="Item not found")
return {"name": fake_items_db[item_id]["name"], "item_id": item_id}
@router.put(
"/{item_id}",
tags=["custom"],
responses={403: {"description": "Operation forbidden"}},
)
async def update_item(item_id: str):
if item_id != "plumbus":
raise HTTPException(
status_code=403, detail="You can only update the item: plumbus"
)
return {"item_id": item_id, "name": "The great Plumbus"}
View on GitHub (pinned to 3e8d1526d8)