tiangolo/fastapi · error · ValueError

Invalid ID format, it must start with "isbn-" or "imdb-"

Error message

Invalid ID format, it must start with "isbn-" or "imdb-"

What it means

This `ValueError` is raised by `check_valid_id`, used as a Pydantic `AfterValidator` on the `id` query parameter of `GET /items/`. Because it is raised inside a validator, Pydantic converts it into a 422 response whose `detail` echoes this message. It enforces that any explicitly supplied `id` must be prefixed with `isbn-` or `imdb-`; omitting `id` is allowed (the route then picks one at random).

Source

Thrown at docs_src/query_params_str_validations/tutorial015_an_py310.py:18

import random
from typing import Annotated

from fastapi import FastAPI
from pydantic import AfterValidator

app = FastAPI()

data = {
    "isbn-9781529046137": "The Hitchhiker's Guide to the Galaxy",
    "imdb-tt0371724": "The Hitchhiker's Guide to the Galaxy",
    "isbn-9781439512982": "Isaac Asimov: The Complete Stories, Vol. 2",
}


def check_valid_id(id: str):
    if not id.startswith(("isbn-", "imdb-")):
        raise ValueError('Invalid ID format, it must start with "isbn-" or "imdb-"')
    return id


@app.get("/items/")
async def read_items(
    id: Annotated[str | None, AfterValidator(check_valid_id)] = None,
):
    if id:
        item = data.get(id)
    else:
        id, item = random.choice(list(data.items()))
    return {"id": id, "name": item}

View on GitHub (pinned to 42a41db11f)

Solutions

  1. Prefix the id correctly, e.g. `?id=isbn-9781529046137`.
  2. Omit `id` to let the route return a random item.
  3. If new prefixes are legitimate, extend `startswith(("isbn-", "imdb-"))` and redeploy.

Example fix

# before
GET /items/?id=9781529046137
# after
GET /items/?id=isbn-9781529046137
Defensive patterns

Strategy: validation

Validate before calling

ALLOWED = ("isbn-", "imdb-")
def valid_id(id_: str | None) -> bool:
    return id_ is None or id_.strip().startswith(ALLOWED)
# only include params={"id": id_} when valid_id(id_) is True

Type guard

def is_valid_id(id_: object) -> bool:
    return isinstance(id_, str) and id_.startswith(("isbn-", "imdb-"))

Try / catch

r = client.get("/items/", params={"id": id_} if id_ else {})
if r.status_code == 422:
    raise ValueError(f"bad id format: {r.json()['detail']}")

Prevention

When it happens

Trigger: `GET /items/?id=9781529046137` (no prefix), `?id=foo-123`, or `?id=isbn` (prefix only). Each maps to a key in the in-memory `data` dict that uses the `isbn-`/`imdb-` prefix scheme.

Common situations: Front-end passing a raw barcode without the source prefix; IDs copied from another system; changing the accepted prefix tuple without updating callers; trailing whitespace breaking `startswith`.

Related errors


AI-assisted analysis of tiangolo/fastapi@42a41db11f (2026-08-04). Data as JSON: /data/errors/9ef8f6ae1df13759.json. Report an issue: GitHub.