{"record":{"id":"621b2a18720e8250","repo":"tiangolo/fastapi","slug":"errors-exc-errors-body-body-decode","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_an_py310.py","lineNumber":19,"sourceCode":"from collections.abc import Callable\nfrom typing import Annotated\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: Annotated[list[int], Body()]):\n    return sum(numbers)\n","sourceCodeStart":1,"sourceCodeEnd":31,"githubUrl":"https://github.com/tiangolo/fastapi/blob/3e8d1526d83a90aaf7d6eb6dc682bf150f180b25/docs_src/custom_request_and_route/tutorial002_an_py310.py#L1-L31","documentation":"This is not a string detail but an HTTPException raised inside a custom APIRoute (ValidationErrorLoggingRoute) that intercepts RequestValidationError. When the request body fails Pydantic validation, the handler reads the raw body, builds a detail object {'errors': [...], 'body': '<raw>'}, and raises HTTPException(422) with that object as detail. It is the Annotated-style variant of the custom-request-and-route tutorial. The 'message' here is the detail-construction expression, not a fixed string.","triggerScenarios":"POST / with a body that is not a valid list[int], e.g. '[1, \"a\"]', '{}', or a non-JSON body. The custom route handler catches the RequestValidationError and re-raises a 422 carrying both the structured errors and the original body bytes.","commonSituations":"Clients sending a single int instead of a list, malformed JSON, or wrong content-type. Because the route overrides the default 422 formatter, the error shape differs from FastAPI's built-in response (it echoes the raw body), which can surprise clients expecting the default schema.","solutions":["Send a JSON array of integers: POST / with body [1,2,3] and Content-Type: application/json.","Validate the body shape on the client before sending.","If you do not want the raw body echoed, revert to FastAPI's default route class."],"exampleFix":"// before\ncurl -X POST -H 'Content-Type: application/json' -d '5' http://localhost:8000/\n// after\ncurl -X POST -H 'Content-Type: application/json' -d '[1,2,3]' http://localhost:8000/","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        # detail has 'errors' and 'body' keys\n        ...","preventionTips":["Always send Content-Type: application/json with a JSON array body.","Validate the payload is list[int] before sending.","Expect the non-default 422 shape (errors + raw body) from this custom route."],"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"}