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

  1. Only PUT /items/plumbus.
  2. If broader mutability is needed, remove the hard-coded check and implement per-item ownership rules.
  3. 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

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)