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
- Prefix the id correctly, e.g. `?id=isbn-9781529046137`.
- Omit `id` to let the route return a random item.
- 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
- Centralize the allowed-prefix list in a constant shared by client and server.
- Strip whitespace before appending the id to the query string.
- Treat a missing prefix as a client bug, not a retryable condition.
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
- {"errors": exc.errors(), "body": body.decode()}
- {"errors": exc.errors(), "body": body.decode()}
- e.errors(include_url=False)
- Expected UploadFile, received: {type(__input_value)}
- No Jessica token provided
AI-assisted analysis of tiangolo/fastapi@42a41db11f (2026-08-04).
Data as JSON: /data/errors/9ef8f6ae1df13759.json.
Report an issue: GitHub.