{"record":{"id":"6a8554dbe71908b3","repo":"tiangolo/fastapi","slug":"errors-exc-errors-body-body-decode-6a8554","errorCode":null,"errorMessage":"{\"errors\": exc.errors(), \"body\": body.decode()}","messagePattern":"\\{\"errors\": exc\\.errors\\(\\), \"body\": body\\.decode\\(\\)\\}","errorType":"http","errorClass":"HTTPException","httpStatus":422,"severity":"error","filePath":"docs_src/custom_request_and_route/tutorial002_py310.py","lineNumber":18,"sourceCode":"from collections.abc import Callable\n\nfrom fastapi import Body, FastAPI, HTTPException, Request, Response\nfrom fastapi.exceptions import RequestValidationError\nfrom fastapi.routing import APIRoute\n\n\nclass ValidationErrorLoggingRoute(APIRoute):\n    def get_route_handler(self) -> Callable:\n        original_route_handler = super().get_route_handler()\n\n        async def custom_route_handler(request: Request) -> Response:\n            try:\n                return await original_route_handler(request)\n            except RequestValidationError as exc:\n                body = await request.body()\n                detail = {\"errors\": exc.errors(), \"body\": body.decode()}\n                raise HTTPException(status_code=422, detail=detail)\n\n        return custom_route_handler\n\n\napp = FastAPI()\napp.router.route_class = ValidationErrorLoggingRoute\n\n\n@app.post(\"/\")\nasync def sum_numbers(numbers: list[int] = Body()):\n    return sum(numbers)\n","sourceCodeStart":1,"sourceCodeEnd":30,"githubUrl":"https://github.com/tiangolo/fastapi/blob/3e8d1526d83a90aaf7d6eb6dc682bf150f180b25/docs_src/custom_request_and_route/tutorial002_py310.py#L1-L30","documentation":"Legacy-style (default-parameter) variant of error 12: the same ValidationErrorLoggingRoute intercepts RequestValidationError on POST / and raises HTTPException(422) with detail {'errors': exc.errors(), 'body': <decoded raw body>}. Identical behavior; only the route signature style differs (numbers: list[int] = Body() vs Annotated).","triggerScenarios":"POST / with a body that fails to parse as list[int]. The custom handler returns the errors plus the raw submitted body.","commonSituations":"Malformed JSON, sending a scalar instead of a list, wrong content-type, or relying on the default FastAPI 422 shape while this route echoes the body.","solutions":["Send a valid JSON array of ints with Content-Type: application/json.","Pre-check the payload client-side.","Use the Annotated variant for forward compatibility."],"exampleFix":"// before\nPOST /  body: 5\n// after\nPOST /  body: [1,2,3]","handlingStrategy":"validation","validationCode":"import json, httpx\ndef is_int_list(body: str) -> bool:\n    try:\n        parsed = json.loads(body)\n    except json.JSONDecodeError:\n        return False\n    return isinstance(parsed, list) and all(isinstance(x, int) for x in parsed)\npayload = '[1,2,3]'\nif is_int_list(payload):\n    resp = httpx.post('http://localhost:8000/', content=payload, headers={'Content-Type': 'application/json'})","typeGuard":"def is_list_of_int(value: object) -> bool:\n    return isinstance(value, list) and all(isinstance(x, int) and not isinstance(x, bool) for x in value)","tryCatchPattern":"try:\n    resp = httpx.post(url, content=payload, headers={'Content-Type': 'application/json'})\n    resp.raise_for_status()\nexcept httpx.HTTPStatusError as e:\n    if e.response.status_code == 422:\n        detail = e.response.json()\n        ...","preventionTips":["Send a JSON array of ints with the correct content type.","Pre-validate client-side.","Note the custom 422 body shape differs from FastAPI's default."],"tags":["fastapi","validation","custom-route","request-body","pydantic"],"backgroundTag":null,"analyzedSha":"3e8d1526d83a90aaf7d6eb6dc682bf150f180b25","analyzedAt":"2026-08-11T02:34:52.986Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}