{"record":{"id":"9ef8f6ae1df13759","repo":"tiangolo/fastapi","slug":"invalid-id-format-it-must-start-with-isbn-or","errorCode":null,"errorMessage":"Invalid ID format, it must start with \"isbn-\" or \"imdb-\"","messagePattern":"Invalid ID format, it must start with \"isbn-\" or \"imdb-\"","errorType":"validation","errorClass":"ValueError","httpStatus":422,"severity":"error","filePath":"docs_src/query_params_str_validations/tutorial015_an_py310.py","lineNumber":18,"sourceCode":"import random\nfrom typing import Annotated\n\nfrom fastapi import FastAPI\nfrom pydantic import AfterValidator\n\napp = FastAPI()\n\ndata = {\n    \"isbn-9781529046137\": \"The Hitchhiker's Guide to the Galaxy\",\n    \"imdb-tt0371724\": \"The Hitchhiker's Guide to the Galaxy\",\n    \"isbn-9781439512982\": \"Isaac Asimov: The Complete Stories, Vol. 2\",\n}\n\n\ndef check_valid_id(id: str):\n    if not id.startswith((\"isbn-\", \"imdb-\")):\n        raise ValueError('Invalid ID format, it must start with \"isbn-\" or \"imdb-\"')\n    return id\n\n\n@app.get(\"/items/\")\nasync def read_items(\n    id: Annotated[str | None, AfterValidator(check_valid_id)] = None,\n):\n    if id:\n        item = data.get(id)\n    else:\n        id, item = random.choice(list(data.items()))\n    return {\"id\": id, \"name\": item}\n","sourceCodeStart":1,"sourceCodeEnd":31,"githubUrl":"https://github.com/tiangolo/fastapi/blob/3e8d1526d83a90aaf7d6eb6dc682bf150f180b25/docs_src/query_params_str_validations/tutorial015_an_py310.py#L1-L31","documentation":"A ValueError raised inside an AfterValidator (check_valid_id) attached to the `id` query parameter. FastAPI/Pydantic converts validator ValueErrors into a 422 RequestValidationError response. The validator requires the id to start with 'isbn-' or 'imdb-'. This shows constraining query-string format via Pydantic AfterValidator.","triggerScenarios":"GET /items/?id=<value> where <value> does not start with 'isbn-' or 'imdb-'. Omitting id entirely is allowed (defaults to None, then random).","commonSituations":"Enforcing ID prefixes / formats on query params. Developers hit 422 when they pass a raw id without the required prefix or with wrong casing.","solutions":["Prefix the id correctly, e.g. GET /items/?id=isbn-9781529046137 or GET /items/?id=imdb-tt0371724.","Drop the id parameter to get a random valid item.","If new prefixes should be allowed, update the tuple passed to str.startswith."],"exampleFix":"// before\ndef check_valid_id(id: str):\n    if not id.startswith((\"isbn-\", \"imdb-\")):\n        raise ValueError('Invalid ID format, it must start with \"isbn-\" or \"imdb-\"')\n    return id\n// after (add prefix and clearer message)\nALLOWED = (\"isbn-\", \"imdb-\")\ndef check_valid_id(id: str):\n    if not id.startswith(ALLOWED):\n        raise ValueError(f'Invalid ID format, it must start with one of {ALLOWED}')\n    return id","handlingStrategy":"validation","validationCode":"def check_valid_id(id: str):\n    if not id.startswith(('isbn-', 'imdb-')):\n        raise ValueError('bad id')\ncheck_valid_id(id)  # raises locally before the request","typeGuard":"def is_valid_id(id: str) -> bool:\n    return id.startswith(('isbn-', 'imdb-'))","tryCatchPattern":"resp = requests.get('http://localhost:8000/items/', params={'id': id})\nif resp.status_code == 422:\n    print('Invalid id format:', resp.json()['detail'])","preventionTips":["Always prefix ids with isbn- or imdb-.","Omit id to get a random valid item.","Update the allowed-prefix tuple when new namespaces are added."],"tags":["fastapi","pydantic","aftervalidator","http-422","query-params"],"backgroundTag":null,"analyzedSha":"3e8d1526d83a90aaf7d6eb6dc682bf150f180b25","analyzedAt":"2026-08-11T02:34:52.986Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}