[{"data":1,"prerenderedAt":2840},["ShallowReactive",2],{"nav":3,"page-\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fcustomising-validation-error-responses\u002F":580,"surround-\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fcustomising-validation-error-responses\u002F":2839},[4,186,386],{"title":5,"path":6,"stem":7,"children":8},"Advanced Pydantic Validation Serialization","\u002Fadvanced-pydantic-validation-serialization","advanced-pydantic-validation-serialization",[9,12,42,66,90,114,150,174],{"title":10,"path":6,"stem":11},"Advanced Pydantic Validation and Serialization","advanced-pydantic-validation-serialization\u002Findex",{"title":13,"path":14,"stem":15,"children":16},"Custom Validators and Field Constraints in Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Findex",[17,18,24,30,36],{"title":13,"path":14,"stem":15},{"title":19,"path":20,"stem":21,"children":22},"Before, After and Wrap Validators in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fbefore-after-and-wrap-validators","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fbefore-after-and-wrap-validators\u002Findex",[23],{"title":19,"path":20,"stem":21},{"title":25,"path":26,"stem":27,"children":28},"Creating Reusable Custom Validators in Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcreating-reusable-custom-validators-in-pydantic","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcreating-reusable-custom-validators-in-pydantic\u002Findex",[29],{"title":25,"path":26,"stem":27},{"title":31,"path":32,"stem":33,"children":34},"Cross-Field Validation Patterns in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcross-field-validation-patterns","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcross-field-validation-patterns\u002Findex",[35],{"title":31,"path":32,"stem":33},{"title":37,"path":38,"stem":39,"children":40},"Pydantic v2 Async Custom Validator: What to Do Instead","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fpydantic-v2-async-custom-validator","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fpydantic-v2-async-custom-validator\u002Findex",[41],{"title":37,"path":38,"stem":39},{"title":43,"path":44,"stem":45,"children":46},"JSON Schema Customization in Pydantic and FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization","advanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Findex",[47,48,54,60],{"title":43,"path":44,"stem":45},{"title":49,"path":50,"stem":51,"children":52},"Customizing OpenAPI Schema Generation in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fcustomizing-openapi-schema-generation-in-fastapi","advanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fcustomizing-openapi-schema-generation-in-fastapi\u002Findex",[53],{"title":49,"path":50,"stem":51},{"title":55,"path":56,"stem":57,"children":58},"Discriminated Unions in OpenAPI with Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fdiscriminated-unions-in-openapi","advanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fdiscriminated-unions-in-openapi\u002Findex",[59],{"title":55,"path":56,"stem":57},{"title":61,"path":62,"stem":63,"children":64},"Examples in the OpenAPI Schema with FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fexamples-in-openapi-schema","advanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fexamples-in-openapi-schema\u002Findex",[65],{"title":61,"path":62,"stem":63},{"title":67,"path":68,"stem":69,"children":70},"Nested Model Serialization in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization","advanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Findex",[71,72,78,84],{"title":67,"path":68,"stem":69},{"title":73,"path":74,"stem":75,"children":76},"Excluding Fields Per Endpoint in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fexcluding-fields-per-endpoint","advanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fexcluding-fields-per-endpoint\u002Findex",[77],{"title":73,"path":74,"stem":75},{"title":79,"path":80,"stem":81,"children":82},"Handling Deeply Nested JSON Models Efficiently","\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fhandling-deeply-nested-json-models-efficiently","advanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fhandling-deeply-nested-json-models-efficiently\u002Findex",[83],{"title":79,"path":80,"stem":81},{"title":85,"path":86,"stem":87,"children":88},"Self-Referencing and Recursive Models in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fself-referencing-and-recursive-models","advanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fself-referencing-and-recursive-models\u002Findex",[89],{"title":85,"path":86,"stem":87},{"title":91,"path":92,"stem":93,"children":94},"Performance Optimization for Pydantic Models in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models","advanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Findex",[95,96,102,108],{"title":91,"path":92,"stem":93},{"title":97,"path":98,"stem":99,"children":100},"model_construct and When to Skip Validation in Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Fmodel-construct-when-to-skip-validation","advanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Fmodel-construct-when-to-skip-validation\u002Findex",[101],{"title":97,"path":98,"stem":99},{"title":103,"path":104,"stem":105,"children":106},"Pydantic Model Serialization Performance in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Fpydantic-model-serialization-performance","advanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Fpydantic-model-serialization-performance\u002Findex",[107],{"title":103,"path":104,"stem":105},{"title":109,"path":110,"stem":111,"children":112},"TypeAdapter for Non-Model Types in Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Ftypeadapter-for-non-model-types","advanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Ftypeadapter-for-non-model-types\u002Findex",[113],{"title":109,"path":110,"stem":111},{"title":115,"path":116,"stem":117,"children":118},"Pydantic V2 Migration Guide for FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Findex",[119,120,126,132,138,144],{"title":115,"path":116,"stem":117},{"title":121,"path":122,"stem":123,"children":124},"Migrate @validator to @field_validator in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrate-validator-to-field-validator","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrate-validator-to-field-validator\u002Findex",[125],{"title":121,"path":122,"stem":123},{"title":127,"path":128,"stem":129,"children":130},"Migrating from Pydantic v1 to v2 Without Breaking APIs","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrating-from-pydantic-v1-to-v2-without-breaking-apis","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrating-from-pydantic-v1-to-v2-without-breaking-apis\u002Findex",[131],{"title":127,"path":128,"stem":129},{"title":133,"path":134,"stem":135,"children":136},"model_config vs class Config in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmodel-config-vs-class-config","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmodel-config-vs-class-config\u002Findex",[137],{"title":133,"path":134,"stem":135},{"title":139,"path":140,"stem":141,"children":142},"Replacing json_encoders with field_serializer in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Freplacing-json-encoders-with-field-serializer","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Freplacing-json-encoders-with-field-serializer\u002Findex",[143],{"title":139,"path":140,"stem":141},{"title":145,"path":146,"stem":147,"children":148},"Migrating @root_validator to @model_validator in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Froot-validator-to-model-validator","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Froot-validator-to-model-validator\u002Findex",[149],{"title":145,"path":146,"stem":147},{"title":151,"path":152,"stem":153,"children":154},"Request Validation Patterns in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Frequest-validation-patterns","advanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Findex",[155,156,162,168],{"title":151,"path":152,"stem":153},{"title":157,"path":158,"stem":159,"children":160},"Optional vs Nullable Fields in Pydantic and FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Foptional-vs-nullable-fields","advanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Foptional-vs-nullable-fields\u002Findex",[161],{"title":157,"path":158,"stem":159},{"title":163,"path":164,"stem":165,"children":166},"Query, Path and Body Parameter Validation in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Fquery-path-and-body-parameter-validation","advanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Fquery-path-and-body-parameter-validation\u002Findex",[167],{"title":163,"path":164,"stem":165},{"title":169,"path":170,"stem":171,"children":172},"Validating File Uploads and Forms in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Fvalidating-file-uploads-and-forms","advanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Fvalidating-file-uploads-and-forms\u002Findex",[173],{"title":169,"path":170,"stem":171},{"title":175,"path":176,"stem":177,"children":178},"Type Hinting and IDE Integration in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration","advanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration\u002Findex",[179,180],{"title":175,"path":176,"stem":177},{"title":181,"path":182,"stem":183,"children":184},"Annotated Dependencies and Reusable Types in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration\u002Fannotated-dependencies-and-reusable-types","advanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration\u002Fannotated-dependencies-and-reusable-types\u002Findex",[185],{"title":181,"path":182,"stem":183},{"title":187,"path":188,"stem":189,"children":190},"Async Background Tasks Observability","\u002Fasync-background-tasks-observability","async-background-tasks-observability",[191,194,224,254,284,308,338,362],{"title":192,"path":188,"stem":193},"Async, Background Tasks, and Observability in FastAPI","async-background-tasks-observability\u002Findex",{"title":195,"path":196,"stem":197,"children":198},"Async Correctness and Concurrency in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Findex",[199,200,206,212,218],{"title":195,"path":196,"stem":197},{"title":201,"path":202,"stem":203,"children":204},"Concurrent Requests with asyncio.gather in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Fconcurrent-requests-with-asyncio-gather","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Fconcurrent-requests-with-asyncio-gather\u002Findex",[205],{"title":201,"path":202,"stem":203},{"title":207,"path":208,"stem":209,"children":210},"FastAPI async def vs def: Performance and When to Use Each","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffastapi-async-def-vs-def-performance","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffastapi-async-def-vs-def-performance\u002Findex",[211],{"title":207,"path":208,"stem":209},{"title":213,"path":214,"stem":215,"children":216},"Fixing Blocking Calls in Async FastAPI Routes","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffixing-blocking-calls-in-async-routes","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffixing-blocking-calls-in-async-routes\u002Findex",[217],{"title":213,"path":214,"stem":215},{"title":219,"path":220,"stem":221,"children":222},"Running Sync Code in a Threadpool in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Frunning-sync-code-in-a-threadpool","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Frunning-sync-code-in-a-threadpool\u002Findex",[223],{"title":219,"path":220,"stem":221},{"title":225,"path":226,"stem":227,"children":228},"Async Database Sessions in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions","async-background-tasks-observability\u002Fasync-database-sessions\u002Findex",[229,230,236,242,248],{"title":225,"path":226,"stem":227},{"title":231,"path":232,"stem":233,"children":234},"Async SQLAlchemy Session per Request in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Fasync-sqlalchemy-session-per-request","async-background-tasks-observability\u002Fasync-database-sessions\u002Fasync-sqlalchemy-session-per-request\u002Findex",[235],{"title":231,"path":232,"stem":233},{"title":237,"path":238,"stem":239,"children":240},"Fixing asyncpg Connection Pool Exhaustion in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Ffixing-asyncpg-pool-exhaustion","async-background-tasks-observability\u002Fasync-database-sessions\u002Ffixing-asyncpg-pool-exhaustion\u002Findex",[241],{"title":237,"path":238,"stem":239},{"title":243,"path":244,"stem":245,"children":246},"Testing with Async Database Fixtures in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Ftesting-with-async-database-fixtures","async-background-tasks-observability\u002Fasync-database-sessions\u002Ftesting-with-async-database-fixtures\u002Findex",[247],{"title":243,"path":244,"stem":245},{"title":249,"path":250,"stem":251,"children":252},"Transaction Management and Rollback in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Ftransaction-management-and-rollback","async-background-tasks-observability\u002Fasync-database-sessions\u002Ftransaction-management-and-rollback\u002Findex",[253],{"title":249,"path":250,"stem":251},{"title":255,"path":256,"stem":257,"children":258},"Background Task Processing in FastAPI","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing","async-background-tasks-observability\u002Fbackground-task-processing\u002Findex",[259,260,266,272,278],{"title":255,"path":256,"stem":257},{"title":261,"path":262,"stem":263,"children":264},"FastAPI BackgroundTasks vs Celery vs ARQ","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Ffastapi-backgroundtasks-vs-celery-vs-arq","async-background-tasks-observability\u002Fbackground-task-processing\u002Ffastapi-backgroundtasks-vs-celery-vs-arq\u002Findex",[265],{"title":261,"path":262,"stem":263},{"title":267,"path":268,"stem":269,"children":270},"Retry and Idempotency for FastAPI Background Tasks","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Fretry-and-idempotency-for-tasks","async-background-tasks-observability\u002Fbackground-task-processing\u002Fretry-and-idempotency-for-tasks\u002Findex",[271],{"title":267,"path":268,"stem":269},{"title":273,"path":274,"stem":275,"children":276},"Running ARQ Workers with FastAPI","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Frunning-arq-workers-with-fastapi","async-background-tasks-observability\u002Fbackground-task-processing\u002Frunning-arq-workers-with-fastapi\u002Findex",[277],{"title":273,"path":274,"stem":275},{"title":279,"path":280,"stem":281,"children":282},"When FastAPI BackgroundTasks Silently Fails","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Fwhen-backgroundtasks-silently-fails","async-background-tasks-observability\u002Fbackground-task-processing\u002Fwhen-backgroundtasks-silently-fails\u002Findex",[283],{"title":279,"path":280,"stem":281},{"title":285,"path":286,"stem":287,"children":288},"Caching Strategies in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies","async-background-tasks-observability\u002Fcaching-strategies\u002Findex",[289,290,296,302],{"title":285,"path":286,"stem":287},{"title":291,"path":292,"stem":293,"children":294},"Cache Invalidation Patterns in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fcache-invalidation-patterns-in-fastapi","async-background-tasks-observability\u002Fcaching-strategies\u002Fcache-invalidation-patterns-in-fastapi\u002Findex",[295],{"title":291,"path":292,"stem":293},{"title":297,"path":298,"stem":299,"children":300},"Caching Dependency Results in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fcaching-dependency-results","async-background-tasks-observability\u002Fcaching-strategies\u002Fcaching-dependency-results\u002Findex",[301],{"title":297,"path":298,"stem":299},{"title":303,"path":304,"stem":305,"children":306},"Redis Response Caching in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fredis-response-caching-in-fastapi","async-background-tasks-observability\u002Fcaching-strategies\u002Fredis-response-caching-in-fastapi\u002Findex",[307],{"title":303,"path":304,"stem":305},{"title":309,"path":310,"stem":311,"children":312},"Observability and Tracing in FastAPI","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing","async-background-tasks-observability\u002Fobservability-and-tracing\u002Findex",[313,314,320,326,332],{"title":309,"path":310,"stem":311},{"title":315,"path":316,"stem":317,"children":318},"Correlating Logs, Traces and Errors in FastAPI","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fcorrelating-logs-traces-and-errors","async-background-tasks-observability\u002Fobservability-and-tracing\u002Fcorrelating-logs-traces-and-errors\u002Findex",[319],{"title":315,"path":316,"stem":317},{"title":321,"path":322,"stem":323,"children":324},"Instrumenting FastAPI with OpenTelemetry","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Finstrumenting-fastapi-with-opentelemetry","async-background-tasks-observability\u002Fobservability-and-tracing\u002Finstrumenting-fastapi-with-opentelemetry\u002Findex",[325],{"title":321,"path":322,"stem":323},{"title":327,"path":328,"stem":329,"children":330},"Prometheus Metrics for FastAPI","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fprometheus-metrics-for-fastapi","async-background-tasks-observability\u002Fobservability-and-tracing\u002Fprometheus-metrics-for-fastapi\u002Findex",[331],{"title":327,"path":328,"stem":329},{"title":333,"path":334,"stem":335,"children":336},"Structured JSON Logging with Request IDs in FastAPI","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fstructured-json-logging-with-request-ids","async-background-tasks-observability\u002Fobservability-and-tracing\u002Fstructured-json-logging-with-request-ids\u002Findex",[337],{"title":333,"path":334,"stem":335},{"title":339,"path":340,"stem":341,"children":342},"Rate Limiting and Throttling in FastAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling","async-background-tasks-observability\u002Frate-limiting-throttling\u002Findex",[343,344,350,356],{"title":339,"path":340,"stem":341},{"title":345,"path":346,"stem":347,"children":348},"FastAPI Rate Limiting with Redis and SlowAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling\u002Ffastapi-rate-limiting-with-redis-slowapi","async-background-tasks-observability\u002Frate-limiting-throttling\u002Ffastapi-rate-limiting-with-redis-slowapi\u002Findex",[349],{"title":345,"path":346,"stem":347},{"title":351,"path":352,"stem":353,"children":354},"Per-User Token Bucket Throttling in FastAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling\u002Fper-user-token-bucket-throttling","async-background-tasks-observability\u002Frate-limiting-throttling\u002Fper-user-token-bucket-throttling\u002Findex",[355],{"title":351,"path":352,"stem":353},{"title":357,"path":358,"stem":359,"children":360},"Rate Limit Headers and 429 Responses in FastAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling\u002Frate-limit-headers-and-429-responses","async-background-tasks-observability\u002Frate-limiting-throttling\u002Frate-limit-headers-and-429-responses\u002Findex",[361],{"title":357,"path":358,"stem":359},{"title":363,"path":364,"stem":365,"children":366},"Testing FastAPI Applications","\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications","async-background-tasks-observability\u002Ftesting-fastapi-applications\u002Findex",[367,368,374,380],{"title":363,"path":364,"stem":365},{"title":369,"path":370,"stem":371,"children":372},"Mocking External Services in FastAPI Tests","\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications\u002Fmocking-external-services-in-tests","async-background-tasks-observability\u002Ftesting-fastapi-applications\u002Fmocking-external-services-in-tests\u002Findex",[373],{"title":369,"path":370,"stem":371},{"title":375,"path":376,"stem":377,"children":378},"TestClient vs httpx AsyncClient in FastAPI","\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications\u002Ftestclient-vs-httpx-asyncclient","async-background-tasks-observability\u002Ftesting-fastapi-applications\u002Ftestclient-vs-httpx-asyncclient\u002Findex",[379],{"title":375,"path":376,"stem":377},{"title":381,"path":382,"stem":383,"children":384},"Testing Async FastAPI Endpoints with pytest-asyncio","\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications\u002Ftesting-async-endpoints-with-pytest-asyncio","async-background-tasks-observability\u002Ftesting-fastapi-applications\u002Ftesting-async-endpoints-with-pytest-asyncio\u002Findex",[385],{"title":381,"path":382,"stem":383},{"title":387,"path":388,"stem":389,"children":390},"Core Architecture Routing Patterns","\u002Fcore-architecture-routing-patterns","core-architecture-routing-patterns",[391,394,412,436,472,496,526,556],{"title":392,"path":388,"stem":393},"FastAPI Core Architecture and Routing Patterns","core-architecture-routing-patterns\u002Findex",{"title":395,"path":396,"stem":397,"children":398},"Application Factory Patterns in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fapplication-factory-patterns","core-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Findex",[399,400,406],{"title":395,"path":396,"stem":397},{"title":401,"path":402,"stem":403,"children":404},"FastAPI App Factory Pattern for Testing and Deployment","\u002Fcore-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Ffastapi-app-factory-pattern-for-testing-and-deployment","core-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Ffastapi-app-factory-pattern-for-testing-and-deployment\u002Findex",[405],{"title":401,"path":402,"stem":403},{"title":407,"path":408,"stem":409,"children":410},"Lifespan Events vs Startup and Shutdown in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Flifespan-events-vs-startup-shutdown","core-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Flifespan-events-vs-startup-shutdown\u002Findex",[411],{"title":407,"path":408,"stem":409},{"title":413,"path":414,"stem":415,"children":416},"Configuration Management in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management","core-architecture-routing-patterns\u002Fconfiguration-management\u002Findex",[417,418,424,430],{"title":413,"path":414,"stem":415},{"title":419,"path":420,"stem":421,"children":422},"Managing Environment Variables with Pydantic Settings","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management\u002Fmanaging-environment-variables-with-pydantic-settings","core-architecture-routing-patterns\u002Fconfiguration-management\u002Fmanaging-environment-variables-with-pydantic-settings\u002Findex",[423],{"title":419,"path":420,"stem":421},{"title":425,"path":426,"stem":427,"children":428},"Pydantic Settings vs Dynaconf vs python-decouple","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management\u002Fpydantic-settings-vs-dynaconf-vs-python-decouple","core-architecture-routing-patterns\u002Fconfiguration-management\u002Fpydantic-settings-vs-dynaconf-vs-python-decouple\u002Findex",[429],{"title":425,"path":426,"stem":427},{"title":431,"path":432,"stem":433,"children":434},"Secrets and .env Files Per Environment in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management\u002Fsecrets-and-env-files-per-environment","core-architecture-routing-patterns\u002Fconfiguration-management\u002Fsecrets-and-env-files-per-environment\u002Findex",[435],{"title":431,"path":432,"stem":433},{"title":437,"path":438,"stem":439,"children":440},"Dependency Injection Strategies in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Findex",[441,442,448,454,460,466],{"title":437,"path":438,"stem":439},{"title":443,"path":444,"stem":445,"children":446},"Best Practices for FastAPI Dependency Injection","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fbest-practices-for-fastapi-dependency-injection","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fbest-practices-for-fastapi-dependency-injection\u002Findex",[447],{"title":443,"path":444,"stem":445},{"title":449,"path":450,"stem":451,"children":452},"Dependency Caching and use_cache in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fdependency-caching-and-use-cache","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fdependency-caching-and-use-cache\u002Findex",[453],{"title":449,"path":450,"stem":451},{"title":455,"path":456,"stem":457,"children":458},"Fixing FastAPI Dependency Injection Circular Imports","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Ffastapi-dependency-injection-circular-import-fix","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Ffastapi-dependency-injection-circular-import-fix\u002Findex",[459],{"title":455,"path":456,"stem":457},{"title":461,"path":462,"stem":463,"children":464},"Overriding Dependencies in FastAPI Tests","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Foverriding-dependencies-in-tests","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Foverriding-dependencies-in-tests\u002Findex",[465],{"title":461,"path":462,"stem":463},{"title":467,"path":468,"stem":469,"children":470},"Yield Dependencies and Cleanup Order in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fyield-dependencies-and-cleanup-order","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fyield-dependencies-and-cleanup-order\u002Findex",[471],{"title":467,"path":468,"stem":469},{"title":473,"path":474,"stem":475,"children":476},"Error Handling and Global Exceptions in FastAPI","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions","core-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Findex",[477,478,484,490],{"title":473,"path":474,"stem":475},{"title":479,"path":480,"stem":481,"children":482},"Customising Validation Error Responses in FastAPI","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fcustomising-validation-error-responses","core-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fcustomising-validation-error-responses\u002Findex",[483],{"title":479,"path":480,"stem":481},{"title":485,"path":486,"stem":487,"children":488},"Global Exception Handlers for Consistent API Responses","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fglobal-exception-handlers-for-consistent-api-responses","core-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fglobal-exception-handlers-for-consistent-api-responses\u002Findex",[489],{"title":485,"path":486,"stem":487},{"title":491,"path":492,"stem":493,"children":494},"HTTPException vs Custom Exception Classes in FastAPI","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fhttpexception-vs-custom-exception-classes","core-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fhttpexception-vs-custom-exception-classes\u002Findex",[495],{"title":491,"path":492,"stem":493},{"title":497,"path":498,"stem":499,"children":500},"Middleware Implementation in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Findex",[501,502,508,514,520],{"title":497,"path":498,"stem":499},{"title":503,"path":504,"stem":505,"children":506},"CORS Middleware Configuration in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fcors-middleware-configuration","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fcors-middleware-configuration\u002Findex",[507],{"title":503,"path":504,"stem":505},{"title":509,"path":510,"stem":511,"children":512},"Implementing Custom Middleware for Request Tracing","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fimplementing-custom-middleware-for-request-tracing","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fimplementing-custom-middleware-for-request-tracing\u002Findex",[513],{"title":509,"path":510,"stem":511},{"title":515,"path":516,"stem":517,"children":518},"Middleware Execution Order in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fmiddleware-execution-order","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fmiddleware-execution-order\u002Findex",[519],{"title":515,"path":516,"stem":517},{"title":521,"path":522,"stem":523,"children":524},"Middleware vs Dependencies: When to Use Which","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fmiddleware-vs-dependencies-when-to-use-which","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fmiddleware-vs-dependencies-when-to-use-which\u002Findex",[525],{"title":521,"path":522,"stem":523},{"title":527,"path":528,"stem":529,"children":530},"Modular Router Organization in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Findex",[531,532,538,544,550],{"title":527,"path":528,"stem":529},{"title":533,"path":534,"stem":535,"children":536},"APIRouter Prefix vs Sub-Application Mounting in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Fapirouter-prefix-vs-sub-application-mounting","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Fapirouter-prefix-vs-sub-application-mounting\u002Findex",[537],{"title":533,"path":534,"stem":535},{"title":539,"path":540,"stem":541,"children":542},"How to Structure Large FastAPI Projects for Scale","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Fhow-to-structure-large-fastapi-projects-for-scale","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Fhow-to-structure-large-fastapi-projects-for-scale\u002Findex",[543],{"title":539,"path":540,"stem":541},{"title":545,"path":546,"stem":547,"children":548},"Router Tags and OpenAPI Grouping in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Frouter-tags-and-openapi-grouping","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Frouter-tags-and-openapi-grouping\u002Findex",[549],{"title":545,"path":546,"stem":547},{"title":551,"path":552,"stem":553,"children":554},"Versioning APIs with FastAPI Routers","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Fversioning-apis-with-routers","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Fversioning-apis-with-routers\u002Findex",[555],{"title":551,"path":552,"stem":553},{"title":557,"path":558,"stem":559,"children":560},"The FastAPI Request\u002FResponse Lifecycle","\u002Fcore-architecture-routing-patterns\u002Frequest-response-lifecycle","core-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Findex",[561,562,568,574],{"title":557,"path":558,"stem":559},{"title":563,"path":564,"stem":565,"children":566},"How a Request Flows Through FastAPI","\u002Fcore-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fhow-a-request-flows-through-fastapi","core-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fhow-a-request-flows-through-fastapi\u002Findex",[567],{"title":563,"path":564,"stem":565},{"title":569,"path":570,"stem":571,"children":572},"Response Model and Serialization Order","\u002Fcore-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fresponse-model-and-serialization-order","core-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fresponse-model-and-serialization-order\u002Findex",[573],{"title":569,"path":570,"stem":571},{"title":575,"path":576,"stem":577,"children":578},"Streaming and File Responses in FastAPI","\u002Fcore-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fstreaming-and-file-responses","core-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fstreaming-and-file-responses\u002Findex",[579],{"title":575,"path":576,"stem":577},{"id":581,"title":479,"body":582,"dateModified":2807,"datePublished":2807,"description":2808,"extension":2809,"faq":2810,"howto":2821,"meta":2822,"navigation":1009,"path":480,"seo":2836,"stem":481,"type":2837,"__hash__":2838},"content\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fcustomising-validation-error-responses\u002Findex.md",{"type":583,"value":584,"toc":2795},"minimark",[585,589,596,665,674,679,693,696,840,844,859,878,895,899,905,1114,1125,1132,1151,1158,1164,1176,1180,1923,1926,1963,1975,1994,2002,2006,2009,2015,2021,2027,2033,2036,2042,2046,2049,2235,2256,2266,2298,2302,2574,2577,2581,2593,2609,2624,2645,2655,2659,2673,2679,2699,2711,2727,2742,2746,2791],[586,587,479],"h1",{"id":588},"customising-validation-error-responses-in-fastapi",[590,591,592],"p",{},[593,594,595],"strong",{},"Key takeaways:",[597,598,599,612,636,655,662],"ul",{},[600,601,602,603,607,608,611],"li",{},"Register ",[604,605,606],"code",{},"@app.exception_handler(RequestValidationError)"," and return your own ",[604,609,610],{},"JSONResponse"," — it replaces FastAPI's built-in handler application-wide.",[600,613,614,617,618,621,622,621,625,621,628,631,632,635],{},[604,615,616],{},"exc.errors()"," gives you the full Pydantic error list: ",[604,619,620],{},"type",", ",[604,623,624],{},"loc",[604,626,627],{},"msg",[604,629,630],{},"input"," and often ",[604,633,634],{},"ctx",".",[600,637,638,641,642,621,645,621,648,621,651,654],{},[604,639,640],{},"loc[0]"," is the location (",[604,643,644],{},"body",[604,646,647],{},"query",[604,649,650],{},"path",[604,652,653],{},"header","); the rest is the field path. Drop the first element for a client-friendly name.",[600,656,657,658,661],{},"Keep the raw errors — in a ",[604,659,660],{},"debug"," block or in your logs — or you lose the most useful diagnostic you have.",[600,663,664],{},"Changing the status code from 422 leaves your OpenAPI schema documenting 422 unless you fix that too.",[590,666,667,668,673],{},"This page builds on ",[669,670,672],"a",{"href":671},"\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002F","global exception handlers"," for the one exception every API hits constantly: a request that failed validation.",[675,676,678],"h2",{"id":677},"the-problem-this-solves","The Problem This Solves",[590,680,681,682,685,686,688,689,692],{},"Your API already has an error contract. Every failure returns ",[604,683,684],{},"{\"error\": {\"code\": ..., \"message\": ..., \"fields\": [...]}}",", your clients parse it, your mobile app maps ",[604,687,604],{}," to a localised string. Then a request fails validation and FastAPI returns ",[604,690,691],{},"{\"detail\": [...]}"," — a completely different shape, with Pydantic's internal vocabulary in it, produced before any of your code ran.",[590,694,695],{},"Now you have two error contracts, and the one you did not design is the one that fires most often.",[697,698,699,836],"figure",{},[700,701,709,710,709,714,709,718,709,727,709,734,709,739,709,745,709,750,709,755,709,760,709,763,709,770,709,775,709,779,709,782,709,786,709,789,709,793,709,799,709,803,709,807,709,810,709,814,709,817,709,820,709,826,709,830],"svg",{"viewBox":702,"role":703,"ariaLabelledBy":704,"xmlns":707,"style":708},"0 0 720 300","img",[705,706],"val-t","val-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;height:auto;max-width:720px;margin:2rem 0","\n  ",[711,712,713],"title",{"id":705},"Where a validation error is turned into a response",[715,716,717],"desc",{"id":706},"Request parsing raises RequestValidationError before the endpoint runs. The registered handler receives the error list and builds either the default detail body or a custom envelope.",[719,720],"rect",{"x":721,"y":722,"width":723,"height":724,"rx":725,"style":726},"24","30","150","50","8","fill:#FFFFFF;stroke:currentColor;stroke-width:1.5px",[728,729,733],"text",{"x":730,"y":731,"style":732},"99","60","text-anchor:middle;fill:currentColor;font:600 13px sans-serif","Request body",[719,735],{"x":736,"y":722,"width":737,"height":724,"rx":725,"style":738},"216","180","fill:#FFFFFF;stroke:#00796B;stroke-width:1.7px",[728,740,744],{"x":741,"y":742,"style":743},"306","52","text-anchor:middle;fill:currentColor;font:600 12px sans-serif","Pydantic validation",[728,746,749],{"x":741,"y":747,"style":748},"69","text-anchor:middle;fill:#4B5563;font:400 11px sans-serif","endpoint has not run",[719,751],{"x":752,"y":722,"width":753,"height":724,"rx":725,"style":754},"438","258","fill:#E0F2F1;stroke:#00796B;stroke-width:1.7px",[728,756,759],{"x":757,"y":742,"style":758},"567","text-anchor:middle;fill:#00695C;font:700 12px sans-serif","RequestValidationError",[728,761,762],{"x":757,"y":747,"style":748},"carries exc.errors()",[764,765],"line",{"x1":766,"y1":767,"x2":768,"y2":767,"style":769},"174","55","212","stroke:#00796B;stroke-width:1.6px",[771,772],"polygon",{"points":773,"style":774},"212,50 222,55 212,60","fill:#00796B",[764,776],{"x1":777,"y1":767,"x2":778,"y2":767,"style":769},"396","432",[771,780],{"points":781,"style":774},"432,50 442,55 432,60",[764,783],{"x1":757,"y1":784,"x2":757,"y2":785,"style":769},"80","118",[771,787],{"points":788,"style":774},"562,118 567,128 572,118",[719,790],{"x":731,"y":723,"width":791,"height":792,"rx":725,"style":726},"280","76",[728,794,798],{"x":795,"y":796,"style":797},"200","176","text-anchor:middle;fill:currentColor;font:700 12px sans-serif","default handler",[728,800,802],{"x":795,"y":801,"style":748},"196","422 with a detail array",[728,804,806],{"x":795,"y":805,"style":748},"214","Pydantic vocabulary",[719,808],{"x":809,"y":723,"width":791,"height":792,"rx":725,"style":754},"380",[728,811,813],{"x":812,"y":796,"style":758},"520","your handler",[728,815,816],{"x":812,"y":801,"style":748},"your envelope and codes",[728,818,819],{"x":812,"y":805,"style":748},"raw detail kept for debugging",[764,821],{"x1":812,"y1":822,"x2":823,"y2":824,"style":825},"128","330","148","stroke:currentColor;stroke-width:1.4px",[764,827],{"x1":828,"y1":822,"x2":829,"y2":824,"style":769},"590","560",[728,831,835],{"x":832,"y":833,"style":834},"360","270","text-anchor:middle;fill:#4B5563;font:400 12px sans-serif","Registering a handler replaces the default for the whole application.",[837,838,839],"figcaption",{},"Validation fails before your endpoint exists in the picture. The only place to shape the response is the handler.",[675,841,843],{"id":842},"why-it-happens-the-default-handler","Why It Happens: the Default Handler",[590,845,846,847,849,850,853,854,858],{},"FastAPI raises ",[604,848,759],{}," when it cannot build the endpoint's declared parameters from the incoming request — a body field of the wrong type, a missing query parameter, a path segment that will not parse as an ",[604,851,852],{},"int",". Note the plural: it collects ",[855,856,857],"em",{},"all"," failures across body, query, path and headers into one list rather than stopping at the first.",[590,860,861,862,865,866,869,870,873,874,877],{},"At application construction, FastAPI installs a default handler for that exception which returns ",[604,863,864],{},"422"," with ",[604,867,868],{},"{\"detail\": exc.errors()}",". It is registered in the same ",[604,871,872],{},"exception_handlers"," mapping that ",[604,875,876],{},"@app.exception_handler"," writes to, so your registration simply replaces it. There is nothing to disable or opt out of.",[590,879,880,881,884,885,889,890,894],{},"The handler runs inside Starlette's ",[604,882,883],{},"ExceptionMiddleware",", which — as ",[669,886,888],{"href":887},"\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fmiddleware-execution-order\u002F","middleware execution order"," shows — sits between your middleware stack and the router. Your envelope is therefore a normal response by the time any middleware sees it, and a correlation ID stamped by ",[669,891,893],{"href":892},"\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fimplementing-custom-middleware-for-request-tracing\u002F","tracing middleware"," can be read inside the handler from the request.",[675,896,898],{"id":897},"the-default-body-for-real","The Default Body, for Real",[590,900,901,902,904],{},"Before replacing something, look at what it produces. This model has three constrained fields and an ",[604,903,852],{}," path parameter:",[906,907,912],"pre",{"className":908,"code":909,"language":910,"meta":911,"style":911},"language-python shiki shiki-themes github-light-high-contrast","class Order(BaseModel):\n    sku: str = Field(min_length=3)\n    quantity: int = Field(gt=0)\n    channel: Literal[\"web\", \"store\"]\n\n\n@app.post(\"\u002Forders\u002F{tenant_id}\")\nasync def create_order(\n    order: Order,\n    tenant_id: Annotated[int, Path()],\n    dry_run: Annotated[bool, Query()] = False,\n):\n    return {\"created\": order.model_dump(), \"tenant_id\": tenant_id}\n","python","",[604,913,914,937,964,986,1004,1011,1016,1036,1051,1057,1068,1088,1093],{"__ignoreMap":911},[915,916,918,922,926,930,934],"span",{"class":764,"line":917},1,[915,919,921],{"class":920},"sTJeM","class",[915,923,925],{"class":924},"sV4o_"," Order",[915,927,929],{"class":928},"sigWx","(",[915,931,933],{"class":932},"sacAq","BaseModel",[915,935,936],{"class":928},"):\n",[915,938,940,943,946,949,952,955,958,961],{"class":764,"line":939},2,[915,941,942],{"class":928},"    sku: ",[915,944,945],{"class":932},"str",[915,947,948],{"class":920}," =",[915,950,951],{"class":928}," Field(",[915,953,954],{"class":924},"min_length",[915,956,957],{"class":920},"=",[915,959,960],{"class":932},"3",[915,962,963],{"class":928},")\n",[915,965,967,970,972,974,976,979,981,984],{"class":764,"line":966},3,[915,968,969],{"class":928},"    quantity: ",[915,971,852],{"class":932},[915,973,948],{"class":920},[915,975,951],{"class":928},[915,977,978],{"class":924},"gt",[915,980,957],{"class":920},[915,982,983],{"class":932},"0",[915,985,963],{"class":928},[915,987,989,992,996,998,1001],{"class":764,"line":988},4,[915,990,991],{"class":928},"    channel: Literal[",[915,993,995],{"class":994},"sYEJz","\"web\"",[915,997,621],{"class":928},[915,999,1000],{"class":994},"\"store\"",[915,1002,1003],{"class":928},"]\n",[915,1005,1007],{"class":764,"line":1006},5,[915,1008,1010],{"emptyLinePlaceholder":1009},true,"\n",[915,1012,1014],{"class":764,"line":1013},6,[915,1015,1010],{"emptyLinePlaceholder":1009},[915,1017,1019,1023,1025,1028,1031,1034],{"class":764,"line":1018},7,[915,1020,1022],{"class":1021},"s3dhs","@app.post",[915,1024,929],{"class":928},[915,1026,1027],{"class":994},"\"\u002Forders\u002F",[915,1029,1030],{"class":920},"{tenant_id}",[915,1032,1033],{"class":994},"\"",[915,1035,963],{"class":928},[915,1037,1039,1042,1045,1048],{"class":764,"line":1038},8,[915,1040,1041],{"class":920},"async",[915,1043,1044],{"class":920}," def",[915,1046,1047],{"class":1021}," create_order",[915,1049,1050],{"class":928},"(\n",[915,1052,1054],{"class":764,"line":1053},9,[915,1055,1056],{"class":928},"    order: Order,\n",[915,1058,1060,1063,1065],{"class":764,"line":1059},10,[915,1061,1062],{"class":928},"    tenant_id: Annotated[",[915,1064,852],{"class":932},[915,1066,1067],{"class":928},", Path()],\n",[915,1069,1071,1074,1077,1080,1082,1085],{"class":764,"line":1070},11,[915,1072,1073],{"class":928},"    dry_run: Annotated[",[915,1075,1076],{"class":932},"bool",[915,1078,1079],{"class":928},", Query()] ",[915,1081,957],{"class":920},[915,1083,1084],{"class":932}," False",[915,1086,1087],{"class":928},",\n",[915,1089,1091],{"class":764,"line":1090},12,[915,1092,936],{"class":928},[915,1094,1096,1099,1102,1105,1108,1111],{"class":764,"line":1095},13,[915,1097,1098],{"class":920},"    return",[915,1100,1101],{"class":928}," {",[915,1103,1104],{"class":994},"\"created\"",[915,1106,1107],{"class":928},": order.model_dump(), ",[915,1109,1110],{"class":994},"\"tenant_id\"",[915,1112,1113],{"class":928},": tenant_id}\n",[590,1115,1116,1117,1120,1121,1124],{},"Posting ",[604,1118,1119],{},"{\"sku\": \"ab\", \"quantity\": 0, \"channel\": \"kiosk\"}"," to ",[604,1122,1123],{},"\u002Forders\u002F7"," produces this — real output from the verification run on FastAPI 0.139.2 with Pydantic 2.13.4:",[906,1126,1130],{"className":1127,"code":1129,"language":728,"meta":911},[1128],"language-text","$ POST \u002Fdefault\u002Forders\u002F7  {\"sku\": \"ab\", \"quantity\": 0, \"channel\": \"kiosk\"}\n422 Unprocessable Entity\n{\n  \"detail\": [\n    {\n      \"type\": \"string_too_short\",\n      \"loc\": [\n        \"body\",\n        \"sku\"\n      ],\n      \"msg\": \"String should have at least 3 characters\",\n      \"input\": \"ab\",\n      \"ctx\": {\n        \"min_length\": 3\n      }\n    },\n    {\n      \"type\": \"greater_than\",\n      \"loc\": [\n        \"body\",\n        \"quantity\"\n      ],\n      \"msg\": \"Input should be greater than 0\",\n      \"input\": 0,\n      \"ctx\": {\n        \"gt\": 0\n      }\n    },\n    {\n      \"type\": \"literal_error\",\n      \"loc\": [\n        \"body\",\n        \"channel\"\n      ],\n      \"msg\": \"Input should be 'web' or 'store'\",\n      \"input\": \"kiosk\",\n      \"ctx\": {\n        \"expected\": \"'web' or 'store'\"\n      }\n    }\n  ]\n}\n",[604,1131,1129],{"__ignoreMap":911},[590,1133,1134,1135,1137,1138,1140,1141,1143,1144,621,1147,1150],{},"Every field you need is already there. ",[604,1136,620],{}," is a stable machine-readable code. ",[604,1139,624],{}," is the path. ",[604,1142,634],{}," carries the constraint values — ",[604,1145,1146],{},"min_length: 3",[604,1148,1149],{},"gt: 0"," — which is what lets you write a message template rather than hard-coding numbers.",[590,1152,1153,1154,1157],{},"A bad path parameter ",[855,1155,1156],{},"and"," a missing body produce one combined list:",[906,1159,1162],{"className":1160,"code":1161,"language":728,"meta":911},[1128],"$ POST \u002Fdefault\u002Forders\u002Fnot-an-int  {}\n422 Unprocessable Entity\n{\n  \"detail\": [\n    {\n      \"type\": \"int_parsing\",\n      \"loc\": [\n        \"path\",\n        \"tenant_id\"\n      ],\n      \"msg\": \"Input should be a valid integer, unable to parse string as an integer\",\n      \"input\": \"not-an-int\"\n    },\n    {\n      \"type\": \"missing\",\n      \"loc\": [\n        \"body\",\n        \"sku\"\n      ],\n      \"msg\": \"Field required\",\n      \"input\": {}\n    },\n    {\n      \"type\": \"missing\",\n      \"loc\": [\n        \"body\",\n        \"quantity\"\n      ],\n      \"msg\": \"Field required\",\n      \"input\": {}\n    },\n    {\n      \"type\": \"missing\",\n      \"loc\": [\n        \"body\",\n        \"channel\"\n      ],\n      \"msg\": \"Field required\",\n      \"input\": {}\n    }\n  ]\n}\n",[604,1163,1161],{"__ignoreMap":911},[590,1165,1166,1167,1169,1170,1172,1173,1175],{},"Note that ",[604,1168,640],{}," differs per entry — ",[604,1171,650],{}," for one, ",[604,1174,644],{}," for the rest. Any handler that assumes everything came from the body will mislabel the first error.",[675,1177,1179],{"id":1178},"the-fix-your-own-envelope","The Fix: Your Own Envelope",[906,1181,1183],{"className":908,"code":1182,"language":910,"meta":911,"style":911},"from fastapi import FastAPI, Request\nfrom fastapi.encoders import jsonable_encoder\nfrom fastapi.exceptions import RequestValidationError\nfrom fastapi.responses import JSONResponse\n\napp = FastAPI()\n\nFIELD_MESSAGES = {\n    \"missing\": \"This field is required.\",\n    \"greater_than\": \"Must be greater than {gt}.\",\n    \"string_too_short\": \"Must be at least {min_length} characters.\",\n    \"literal_error\": \"Must be one of: {expected}.\",\n    \"int_parsing\": \"Must be a whole number.\",\n    \"bool_parsing\": \"Must be true or false.\",\n}\n\n\ndef field_path(loc: tuple) -> str:\n    \"\"\"Drop the body\u002Fquery\u002Fpath prefix and dot-join the rest, so clients see 'sku'.\"\"\"\n    parts = [str(p) for p in loc[1:]] or [str(p) for p in loc]\n    return \".\".join(parts)\n\n\ndef humanise(error: dict) -> str:\n    template = FIELD_MESSAGES.get(error[\"type\"])\n    if template is None:\n        return error[\"msg\"]                       # Fall back to Pydantic's wording.\n    ctx = {k: str(v) for k, v in (error.get(\"ctx\") or {}).items()}\n    try:\n        return template.format(**ctx)\n    except KeyError:\n        return error[\"msg\"]\n\n\n@app.exception_handler(RequestValidationError)\nasync def validation_handler(request: Request, exc: RequestValidationError):\n    errors = exc.errors()\n    return JSONResponse(\n        status_code=422,\n        content=jsonable_encoder(\n            {\n                \"error\": {\n                    \"code\": \"validation_failed\",\n                    \"message\": \"The request body failed validation.\",\n                    \"fields\": [\n                        {\n                            \"field\": field_path(e[\"loc\"]),\n                            \"in\": e[\"loc\"][0] if e[\"loc\"] else \"body\",\n                            \"reason\": e[\"type\"],\n                            \"message\": humanise(e),\n                        }\n                        for e in errors\n                    ],\n                    # Keep the raw Pydantic detail for debugging \u002F log correlation.\n                    \"debug\": {\"pydantic_errors\": errors},\n                },\n                \"request_id\": request.headers.get(\"x-request-id\", \"-\"),\n            }\n        ),\n    )\n",[604,1184,1185,1199,1211,1223,1235,1239,1249,1253,1263,1276,1294,1312,1329,1341,1354,1360,1365,1370,1393,1399,1451,1462,1467,1472,1492,1512,1529,1548,1585,1593,1607,1618,1629,1634,1639,1647,1660,1671,1679,1691,1702,1708,1717,1730,1743,1752,1758,1773,1810,1823,1832,1838,1852,1858,1864,1879,1885,1905,1911,1917],{"__ignoreMap":911},[915,1186,1187,1190,1193,1196],{"class":764,"line":917},[915,1188,1189],{"class":920},"from",[915,1191,1192],{"class":928}," fastapi ",[915,1194,1195],{"class":920},"import",[915,1197,1198],{"class":928}," FastAPI, Request\n",[915,1200,1201,1203,1206,1208],{"class":764,"line":939},[915,1202,1189],{"class":920},[915,1204,1205],{"class":928}," fastapi.encoders ",[915,1207,1195],{"class":920},[915,1209,1210],{"class":928}," jsonable_encoder\n",[915,1212,1213,1215,1218,1220],{"class":764,"line":966},[915,1214,1189],{"class":920},[915,1216,1217],{"class":928}," fastapi.exceptions ",[915,1219,1195],{"class":920},[915,1221,1222],{"class":928}," RequestValidationError\n",[915,1224,1225,1227,1230,1232],{"class":764,"line":988},[915,1226,1189],{"class":920},[915,1228,1229],{"class":928}," fastapi.responses ",[915,1231,1195],{"class":920},[915,1233,1234],{"class":928}," JSONResponse\n",[915,1236,1237],{"class":764,"line":1006},[915,1238,1010],{"emptyLinePlaceholder":1009},[915,1240,1241,1244,1246],{"class":764,"line":1013},[915,1242,1243],{"class":928},"app ",[915,1245,957],{"class":920},[915,1247,1248],{"class":928}," FastAPI()\n",[915,1250,1251],{"class":764,"line":1018},[915,1252,1010],{"emptyLinePlaceholder":1009},[915,1254,1255,1258,1260],{"class":764,"line":1038},[915,1256,1257],{"class":932},"FIELD_MESSAGES",[915,1259,948],{"class":920},[915,1261,1262],{"class":928}," {\n",[915,1264,1265,1268,1271,1274],{"class":764,"line":1053},[915,1266,1267],{"class":994},"    \"missing\"",[915,1269,1270],{"class":928},": ",[915,1272,1273],{"class":994},"\"This field is required.\"",[915,1275,1087],{"class":928},[915,1277,1278,1281,1283,1286,1289,1292],{"class":764,"line":1059},[915,1279,1280],{"class":994},"    \"greater_than\"",[915,1282,1270],{"class":928},[915,1284,1285],{"class":994},"\"Must be greater than ",[915,1287,1288],{"class":920},"{gt}",[915,1290,1291],{"class":994},".\"",[915,1293,1087],{"class":928},[915,1295,1296,1299,1301,1304,1307,1310],{"class":764,"line":1070},[915,1297,1298],{"class":994},"    \"string_too_short\"",[915,1300,1270],{"class":928},[915,1302,1303],{"class":994},"\"Must be at least ",[915,1305,1306],{"class":920},"{min_length}",[915,1308,1309],{"class":994}," characters.\"",[915,1311,1087],{"class":928},[915,1313,1314,1317,1319,1322,1325,1327],{"class":764,"line":1090},[915,1315,1316],{"class":994},"    \"literal_error\"",[915,1318,1270],{"class":928},[915,1320,1321],{"class":994},"\"Must be one of: ",[915,1323,1324],{"class":920},"{expected}",[915,1326,1291],{"class":994},[915,1328,1087],{"class":928},[915,1330,1331,1334,1336,1339],{"class":764,"line":1095},[915,1332,1333],{"class":994},"    \"int_parsing\"",[915,1335,1270],{"class":928},[915,1337,1338],{"class":994},"\"Must be a whole number.\"",[915,1340,1087],{"class":928},[915,1342,1344,1347,1349,1352],{"class":764,"line":1343},14,[915,1345,1346],{"class":994},"    \"bool_parsing\"",[915,1348,1270],{"class":928},[915,1350,1351],{"class":994},"\"Must be true or false.\"",[915,1353,1087],{"class":928},[915,1355,1357],{"class":764,"line":1356},15,[915,1358,1359],{"class":928},"}\n",[915,1361,1363],{"class":764,"line":1362},16,[915,1364,1010],{"emptyLinePlaceholder":1009},[915,1366,1368],{"class":764,"line":1367},17,[915,1369,1010],{"emptyLinePlaceholder":1009},[915,1371,1373,1376,1379,1382,1385,1388,1390],{"class":764,"line":1372},18,[915,1374,1375],{"class":920},"def",[915,1377,1378],{"class":1021}," field_path",[915,1380,1381],{"class":928},"(loc: ",[915,1383,1384],{"class":932},"tuple",[915,1386,1387],{"class":928},") -> ",[915,1389,945],{"class":932},[915,1391,1392],{"class":928},":\n",[915,1394,1396],{"class":764,"line":1395},19,[915,1397,1398],{"class":994},"    \"\"\"Drop the body\u002Fquery\u002Fpath prefix and dot-join the rest, so clients see 'sku'.\"\"\"\n",[915,1400,1402,1405,1407,1410,1412,1415,1418,1421,1424,1427,1430,1433,1436,1438,1440,1442,1444,1446,1448],{"class":764,"line":1401},20,[915,1403,1404],{"class":928},"    parts ",[915,1406,957],{"class":920},[915,1408,1409],{"class":928}," [",[915,1411,945],{"class":932},[915,1413,1414],{"class":928},"(p) ",[915,1416,1417],{"class":920},"for",[915,1419,1420],{"class":928}," p ",[915,1422,1423],{"class":920},"in",[915,1425,1426],{"class":928}," loc[",[915,1428,1429],{"class":932},"1",[915,1431,1432],{"class":928},":]] ",[915,1434,1435],{"class":920},"or",[915,1437,1409],{"class":928},[915,1439,945],{"class":932},[915,1441,1414],{"class":928},[915,1443,1417],{"class":920},[915,1445,1420],{"class":928},[915,1447,1423],{"class":920},[915,1449,1450],{"class":928}," loc]\n",[915,1452,1454,1456,1459],{"class":764,"line":1453},21,[915,1455,1098],{"class":920},[915,1457,1458],{"class":994}," \".\"",[915,1460,1461],{"class":928},".join(parts)\n",[915,1463,1465],{"class":764,"line":1464},22,[915,1466,1010],{"emptyLinePlaceholder":1009},[915,1468,1470],{"class":764,"line":1469},23,[915,1471,1010],{"emptyLinePlaceholder":1009},[915,1473,1475,1477,1480,1483,1486,1488,1490],{"class":764,"line":1474},24,[915,1476,1375],{"class":920},[915,1478,1479],{"class":1021}," humanise",[915,1481,1482],{"class":928},"(error: ",[915,1484,1485],{"class":932},"dict",[915,1487,1387],{"class":928},[915,1489,945],{"class":932},[915,1491,1392],{"class":928},[915,1493,1495,1498,1500,1503,1506,1509],{"class":764,"line":1494},25,[915,1496,1497],{"class":928},"    template ",[915,1499,957],{"class":920},[915,1501,1502],{"class":932}," FIELD_MESSAGES",[915,1504,1505],{"class":928},".get(error[",[915,1507,1508],{"class":994},"\"type\"",[915,1510,1511],{"class":928},"])\n",[915,1513,1515,1518,1521,1524,1527],{"class":764,"line":1514},26,[915,1516,1517],{"class":920},"    if",[915,1519,1520],{"class":928}," template ",[915,1522,1523],{"class":920},"is",[915,1525,1526],{"class":932}," None",[915,1528,1392],{"class":928},[915,1530,1532,1535,1538,1541,1544],{"class":764,"line":1531},27,[915,1533,1534],{"class":920},"        return",[915,1536,1537],{"class":928}," error[",[915,1539,1540],{"class":994},"\"msg\"",[915,1542,1543],{"class":928},"]                       ",[915,1545,1547],{"class":1546},"sFeEa","# Fall back to Pydantic's wording.\n",[915,1549,1551,1554,1556,1559,1561,1564,1566,1569,1571,1574,1577,1580,1582],{"class":764,"line":1550},28,[915,1552,1553],{"class":928},"    ctx ",[915,1555,957],{"class":920},[915,1557,1558],{"class":928}," {k: ",[915,1560,945],{"class":932},[915,1562,1563],{"class":928},"(v) ",[915,1565,1417],{"class":920},[915,1567,1568],{"class":928}," k, v ",[915,1570,1423],{"class":920},[915,1572,1573],{"class":928}," (error.get(",[915,1575,1576],{"class":994},"\"ctx\"",[915,1578,1579],{"class":928},") ",[915,1581,1435],{"class":920},[915,1583,1584],{"class":928}," {}).items()}\n",[915,1586,1588,1591],{"class":764,"line":1587},29,[915,1589,1590],{"class":920},"    try",[915,1592,1392],{"class":928},[915,1594,1596,1598,1601,1604],{"class":764,"line":1595},30,[915,1597,1534],{"class":920},[915,1599,1600],{"class":928}," template.format(",[915,1602,1603],{"class":920},"**",[915,1605,1606],{"class":928},"ctx)\n",[915,1608,1610,1613,1616],{"class":764,"line":1609},31,[915,1611,1612],{"class":920},"    except",[915,1614,1615],{"class":932}," KeyError",[915,1617,1392],{"class":928},[915,1619,1621,1623,1625,1627],{"class":764,"line":1620},32,[915,1622,1534],{"class":920},[915,1624,1537],{"class":928},[915,1626,1540],{"class":994},[915,1628,1003],{"class":928},[915,1630,1632],{"class":764,"line":1631},33,[915,1633,1010],{"emptyLinePlaceholder":1009},[915,1635,1637],{"class":764,"line":1636},34,[915,1638,1010],{"emptyLinePlaceholder":1009},[915,1640,1642,1644],{"class":764,"line":1641},35,[915,1643,876],{"class":1021},[915,1645,1646],{"class":928},"(RequestValidationError)\n",[915,1648,1650,1652,1654,1657],{"class":764,"line":1649},36,[915,1651,1041],{"class":920},[915,1653,1044],{"class":920},[915,1655,1656],{"class":1021}," validation_handler",[915,1658,1659],{"class":928},"(request: Request, exc: RequestValidationError):\n",[915,1661,1663,1666,1668],{"class":764,"line":1662},37,[915,1664,1665],{"class":928},"    errors ",[915,1667,957],{"class":920},[915,1669,1670],{"class":928}," exc.errors()\n",[915,1672,1674,1676],{"class":764,"line":1673},38,[915,1675,1098],{"class":920},[915,1677,1678],{"class":928}," JSONResponse(\n",[915,1680,1682,1685,1687,1689],{"class":764,"line":1681},39,[915,1683,1684],{"class":924},"        status_code",[915,1686,957],{"class":920},[915,1688,864],{"class":932},[915,1690,1087],{"class":928},[915,1692,1694,1697,1699],{"class":764,"line":1693},40,[915,1695,1696],{"class":924},"        content",[915,1698,957],{"class":920},[915,1700,1701],{"class":928},"jsonable_encoder(\n",[915,1703,1705],{"class":764,"line":1704},41,[915,1706,1707],{"class":928},"            {\n",[915,1709,1711,1714],{"class":764,"line":1710},42,[915,1712,1713],{"class":994},"                \"error\"",[915,1715,1716],{"class":928},": {\n",[915,1718,1720,1723,1725,1728],{"class":764,"line":1719},43,[915,1721,1722],{"class":994},"                    \"code\"",[915,1724,1270],{"class":928},[915,1726,1727],{"class":994},"\"validation_failed\"",[915,1729,1087],{"class":928},[915,1731,1733,1736,1738,1741],{"class":764,"line":1732},44,[915,1734,1735],{"class":994},"                    \"message\"",[915,1737,1270],{"class":928},[915,1739,1740],{"class":994},"\"The request body failed validation.\"",[915,1742,1087],{"class":928},[915,1744,1746,1749],{"class":764,"line":1745},45,[915,1747,1748],{"class":994},"                    \"fields\"",[915,1750,1751],{"class":928},": [\n",[915,1753,1755],{"class":764,"line":1754},46,[915,1756,1757],{"class":928},"                        {\n",[915,1759,1761,1764,1767,1770],{"class":764,"line":1760},47,[915,1762,1763],{"class":994},"                            \"field\"",[915,1765,1766],{"class":928},": field_path(e[",[915,1768,1769],{"class":994},"\"loc\"",[915,1771,1772],{"class":928},"]),\n",[915,1774,1776,1779,1782,1784,1787,1789,1792,1795,1798,1800,1802,1805,1808],{"class":764,"line":1775},48,[915,1777,1778],{"class":994},"                            \"in\"",[915,1780,1781],{"class":928},": e[",[915,1783,1769],{"class":994},[915,1785,1786],{"class":928},"][",[915,1788,983],{"class":932},[915,1790,1791],{"class":928},"] ",[915,1793,1794],{"class":920},"if",[915,1796,1797],{"class":928}," e[",[915,1799,1769],{"class":994},[915,1801,1791],{"class":928},[915,1803,1804],{"class":920},"else",[915,1806,1807],{"class":994}," \"body\"",[915,1809,1087],{"class":928},[915,1811,1813,1816,1818,1820],{"class":764,"line":1812},49,[915,1814,1815],{"class":994},"                            \"reason\"",[915,1817,1781],{"class":928},[915,1819,1508],{"class":994},[915,1821,1822],{"class":928},"],\n",[915,1824,1826,1829],{"class":764,"line":1825},50,[915,1827,1828],{"class":994},"                            \"message\"",[915,1830,1831],{"class":928},": humanise(e),\n",[915,1833,1835],{"class":764,"line":1834},51,[915,1836,1837],{"class":928},"                        }\n",[915,1839,1841,1844,1847,1849],{"class":764,"line":1840},52,[915,1842,1843],{"class":920},"                        for",[915,1845,1846],{"class":928}," e ",[915,1848,1423],{"class":920},[915,1850,1851],{"class":928}," errors\n",[915,1853,1855],{"class":764,"line":1854},53,[915,1856,1857],{"class":928},"                    ],\n",[915,1859,1861],{"class":764,"line":1860},54,[915,1862,1863],{"class":1546},"                    # Keep the raw Pydantic detail for debugging \u002F log correlation.\n",[915,1865,1867,1870,1873,1876],{"class":764,"line":1866},55,[915,1868,1869],{"class":994},"                    \"debug\"",[915,1871,1872],{"class":928},": {",[915,1874,1875],{"class":994},"\"pydantic_errors\"",[915,1877,1878],{"class":928},": errors},\n",[915,1880,1882],{"class":764,"line":1881},56,[915,1883,1884],{"class":928},"                },\n",[915,1886,1888,1891,1894,1897,1899,1902],{"class":764,"line":1887},57,[915,1889,1890],{"class":994},"                \"request_id\"",[915,1892,1893],{"class":928},": request.headers.get(",[915,1895,1896],{"class":994},"\"x-request-id\"",[915,1898,621],{"class":928},[915,1900,1901],{"class":994},"\"-\"",[915,1903,1904],{"class":928},"),\n",[915,1906,1908],{"class":764,"line":1907},58,[915,1909,1910],{"class":928},"            }\n",[915,1912,1914],{"class":764,"line":1913},59,[915,1915,1916],{"class":928},"        ),\n",[915,1918,1920],{"class":764,"line":1919},60,[915,1921,1922],{"class":928},"    )\n",[590,1924,1925],{},"Four decisions in there are worth calling out.",[590,1927,1928,1934,1935,1937,1938,1940,1941,1944,1945,1944,1948,1951,1952,1954,1955,1958,1959,1962],{},[593,1929,1930,1933],{},[604,1931,1932],{},"jsonable_encoder"," is not optional."," ",[604,1936,616],{}," embeds the offending ",[604,1939,630],{},", which can be a ",[604,1942,1943],{},"Decimal",", a ",[604,1946,1947],{},"datetime",[604,1949,1950],{},"set",", or an arbitrary object the client sent. Passing the raw list to ",[604,1953,610],{}," raises a serialisation error ",[855,1956,1957],{},"inside your exception handler"," — an unhandled ",[604,1960,1961],{},"500"," in the code that exists to prevent unhandled errors.",[590,1964,1965,1974],{},[593,1966,1967,1969,1970,1973],{},[604,1968,620],{}," becomes ",[604,1971,1972],{},"reason",", unchanged."," Resist rewriting Pydantic's codes into your own taxonomy. They are stable, documented and searchable; a client that wants to distinguish \"too short\" from \"missing\" gets a reliable key for free.",[590,1976,1977,1934,1982,1985,1986,1989,1990,1993],{},[593,1978,1979,1981],{},[604,1980,634],{}," drives the message template.",[604,1983,1984],{},"Must be at least {min_length} characters"," renders correctly for a field with ",[604,1987,1988],{},"min_length=3"," and for one with ",[604,1991,1992],{},"min_length=64",", with no per-field configuration.",[590,1995,1996,2001],{},[593,1997,1998,1999,635],{},"Unknown types fall through to ",[604,2000,627],{}," Pydantic has dozens of error types and you will not enumerate them all. Falling back to the original wording means an unmapped validator degrades to something slightly technical rather than to a blank string.",[675,2003,2005],{"id":2004},"the-result-for-real","The Result, for Real",[590,2007,2008],{},"Same two requests, same models, through the customised application:",[906,2010,2013],{"className":2011,"code":2012,"language":728,"meta":911},[1128],"$ POST \u002Fcustom\u002Forders\u002F7  {\"sku\": \"ab\", \"quantity\": 0, \"channel\": \"kiosk\"}\n422 Unprocessable Entity\n{\n  \"error\": {\n    \"code\": \"validation_failed\",\n    \"message\": \"The request body failed validation.\",\n    \"fields\": [\n      {\n        \"field\": \"sku\",\n        \"in\": \"body\",\n        \"reason\": \"string_too_short\",\n        \"message\": \"Must be at least 3 characters.\"\n      },\n      {\n        \"field\": \"quantity\",\n        \"in\": \"body\",\n        \"reason\": \"greater_than\",\n        \"message\": \"Must be greater than 0.\"\n      },\n      {\n        \"field\": \"channel\",\n        \"in\": \"body\",\n        \"reason\": \"literal_error\",\n        \"message\": \"Must be one of: 'web' or 'store'.\"\n      }\n    ],\n    \"debug\": {\n      \"pydantic_errors\": [\n        {\n          \"type\": \"string_too_short\",\n          \"loc\": [\n            \"body\",\n            \"sku\"\n          ],\n          \"msg\": \"String should have at least 3 characters\",\n          \"input\": \"ab\",\n          \"ctx\": {\n            \"min_length\": 3\n          }\n        },\n        {\n          \"type\": \"greater_than\",\n          \"loc\": [\n            \"body\",\n            \"quantity\"\n          ],\n          \"msg\": \"Input should be greater than 0\",\n          \"input\": 0,\n          \"ctx\": {\n            \"gt\": 0\n          }\n        },\n        {\n          \"type\": \"literal_error\",\n          \"loc\": [\n            \"body\",\n            \"channel\"\n          ],\n          \"msg\": \"Input should be 'web' or 'store'\",\n          \"input\": \"kiosk\",\n          \"ctx\": {\n            \"expected\": \"'web' or 'store'\"\n          }\n        }\n      ]\n    }\n  },\n  \"request_id\": \"-\"\n}\n",[604,2014,2012],{"__ignoreMap":911},[590,2016,2017,2018,2020],{},"Every friendly message came out of the ",[604,2019,634],{}," values in the block below it — nothing about \"3 characters\" or \"greater than 0\" is hard-coded per field.",[590,2022,2023,2024,2026],{},"And the mixed path-plus-body case, where ",[604,2025,1423],{}," correctly distinguishes the two sources:",[906,2028,2031],{"className":2029,"code":2030,"language":728,"meta":911},[1128],"$ POST \u002Fcustom\u002Forders\u002Fnot-an-int  {}\n422 Unprocessable Entity\n{\n  \"error\": {\n    \"code\": \"validation_failed\",\n    \"message\": \"The request body failed validation.\",\n    \"fields\": [\n      {\n        \"field\": \"tenant_id\",\n        \"in\": \"path\",\n        \"reason\": \"int_parsing\",\n        \"message\": \"Must be a whole number.\"\n      },\n      {\n        \"field\": \"sku\",\n        \"in\": \"body\",\n        \"reason\": \"missing\",\n        \"message\": \"This field is required.\"\n      },\n      {\n        \"field\": \"quantity\",\n        \"in\": \"body\",\n        \"reason\": \"missing\",\n        \"message\": \"This field is required.\"\n      },\n      {\n        \"field\": \"channel\",\n        \"in\": \"body\",\n        \"reason\": \"missing\",\n        \"message\": \"This field is required.\"\n      }\n    ],\n    \"debug\": {\n      \"pydantic_errors\": [\n        {\n          \"type\": \"int_parsing\",\n          \"loc\": [\n            \"path\",\n            \"tenant_id\"\n          ],\n          \"msg\": \"Input should be a valid integer, unable to parse string as an integer\",\n          \"input\": \"not-an-int\"\n        },\n        {\n          \"type\": \"missing\",\n          \"loc\": [\n            \"body\",\n            \"sku\"\n          ],\n          \"msg\": \"Field required\",\n          \"input\": {}\n        },\n        {\n          \"type\": \"missing\",\n          \"loc\": [\n            \"body\",\n            \"quantity\"\n          ],\n          \"msg\": \"Field required\",\n          \"input\": {}\n        },\n        {\n          \"type\": \"missing\",\n          \"loc\": [\n            \"body\",\n            \"channel\"\n          ],\n          \"msg\": \"Field required\",\n          \"input\": {}\n        }\n      ]\n    }\n  },\n  \"request_id\": \"-\"\n}\n",[604,2032,2030],{"__ignoreMap":911},[590,2034,2035],{},"A valid request is unaffected — the handler only fires on failure:",[906,2037,2040],{"className":2038,"code":2039,"language":728,"meta":911},[1128],"$ POST \u002Fcustom\u002Forders\u002F7  {\"sku\": \"abc-123\", \"quantity\": 2, \"channel\": \"web\"}\n200 OK\n{\n  \"created\": {\n    \"sku\": \"abc-123\",\n    \"quantity\": 2,\n    \"channel\": \"web\"\n  },\n  \"tenant_id\": 7\n}\n",[604,2041,2039],{"__ignoreMap":911},[675,2043,2045],{"id":2044},"matching-an-existing-error-contract","Matching an Existing Error Contract",[590,2047,2048],{},"The point of all this is usually consistency with errors your API already returns. Make the validation handler and your other handlers share one builder:",[906,2050,2052],{"className":908,"code":2051,"language":910,"meta":911,"style":911},"def error_response(status: int, code: str, message: str, *, fields=None, debug=None, request=None):\n    body = {\"error\": {\"code\": code, \"message\": message}}\n    if fields:\n        body[\"error\"][\"fields\"] = fields\n    if debug and settings.expose_error_detail:\n        body[\"error\"][\"debug\"] = debug\n    body[\"request_id\"] = request_id_ctx.get() if request else \"-\"\n    return JSONResponse(status_code=status, content=jsonable_encoder(body))\n",[604,2053,2054,2105,2131,2138,2157,2169,2187,2212],{"__ignoreMap":911},[915,2055,2056,2058,2061,2064,2066,2069,2071,2074,2076,2078,2081,2084,2086,2089,2092,2094,2096,2099,2101,2103],{"class":764,"line":917},[915,2057,1375],{"class":920},[915,2059,2060],{"class":1021}," error_response",[915,2062,2063],{"class":928},"(status: ",[915,2065,852],{"class":932},[915,2067,2068],{"class":928},", code: ",[915,2070,945],{"class":932},[915,2072,2073],{"class":928},", message: ",[915,2075,945],{"class":932},[915,2077,621],{"class":928},[915,2079,2080],{"class":920},"*",[915,2082,2083],{"class":928},", fields",[915,2085,957],{"class":920},[915,2087,2088],{"class":932},"None",[915,2090,2091],{"class":928},", debug",[915,2093,957],{"class":920},[915,2095,2088],{"class":932},[915,2097,2098],{"class":928},", request",[915,2100,957],{"class":920},[915,2102,2088],{"class":932},[915,2104,936],{"class":928},[915,2106,2107,2110,2112,2114,2117,2119,2122,2125,2128],{"class":764,"line":939},[915,2108,2109],{"class":928},"    body ",[915,2111,957],{"class":920},[915,2113,1101],{"class":928},[915,2115,2116],{"class":994},"\"error\"",[915,2118,1872],{"class":928},[915,2120,2121],{"class":994},"\"code\"",[915,2123,2124],{"class":928},": code, ",[915,2126,2127],{"class":994},"\"message\"",[915,2129,2130],{"class":928},": message}}\n",[915,2132,2133,2135],{"class":764,"line":966},[915,2134,1517],{"class":920},[915,2136,2137],{"class":928}," fields:\n",[915,2139,2140,2143,2145,2147,2150,2152,2154],{"class":764,"line":988},[915,2141,2142],{"class":928},"        body[",[915,2144,2116],{"class":994},[915,2146,1786],{"class":928},[915,2148,2149],{"class":994},"\"fields\"",[915,2151,1791],{"class":928},[915,2153,957],{"class":920},[915,2155,2156],{"class":928}," fields\n",[915,2158,2159,2161,2164,2166],{"class":764,"line":1006},[915,2160,1517],{"class":920},[915,2162,2163],{"class":928}," debug ",[915,2165,1156],{"class":920},[915,2167,2168],{"class":928}," settings.expose_error_detail:\n",[915,2170,2171,2173,2175,2177,2180,2182,2184],{"class":764,"line":1013},[915,2172,2142],{"class":928},[915,2174,2116],{"class":994},[915,2176,1786],{"class":928},[915,2178,2179],{"class":994},"\"debug\"",[915,2181,1791],{"class":928},[915,2183,957],{"class":920},[915,2185,2186],{"class":928}," debug\n",[915,2188,2189,2192,2195,2197,2199,2202,2204,2207,2209],{"class":764,"line":1018},[915,2190,2191],{"class":928},"    body[",[915,2193,2194],{"class":994},"\"request_id\"",[915,2196,1791],{"class":928},[915,2198,957],{"class":920},[915,2200,2201],{"class":928}," request_id_ctx.get() ",[915,2203,1794],{"class":920},[915,2205,2206],{"class":928}," request ",[915,2208,1804],{"class":920},[915,2210,2211],{"class":994}," \"-\"\n",[915,2213,2214,2216,2219,2222,2224,2227,2230,2232],{"class":764,"line":1038},[915,2215,1098],{"class":920},[915,2217,2218],{"class":928}," JSONResponse(",[915,2220,2221],{"class":924},"status_code",[915,2223,957],{"class":920},[915,2225,2226],{"class":928},"status, ",[915,2228,2229],{"class":924},"content",[915,2231,957],{"class":920},[915,2233,2234],{"class":928},"jsonable_encoder(body))\n",[590,2236,2237,2238,621,2240,2243,2244,2248,2249,2252,2253,2255],{},"Every handler — ",[604,2239,759],{},[604,2241,2242],{},"HTTPException",", your domain exceptions from ",[669,2245,2247],{"href":2246},"\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fhttpexception-vs-custom-exception-classes\u002F","HTTPException vs custom exception classes"," — calls this. One shape, one place to change it. The ",[604,2250,2251],{},"settings.expose_error_detail"," gate is what lets you keep ",[604,2254,660],{}," in staging and drop it in production without a second code path.",[590,2257,2258,2259,2262,2263,2265],{},"If your contract requires ",[604,2260,2261],{},"400"," rather than ",[604,2264,864],{},", change the status in the handler — but also fix the schema, or every generated client will expect a body shape at a status code you never return:",[906,2267,2269],{"className":908,"code":2268,"language":910,"meta":911,"style":911},"app = FastAPI(responses={400: {\"model\": ErrorEnvelope}})\n",[604,2270,2271],{"__ignoreMap":911},[915,2272,2273,2275,2277,2280,2283,2285,2288,2290,2292,2295],{"class":764,"line":917},[915,2274,1243],{"class":928},[915,2276,957],{"class":920},[915,2278,2279],{"class":928}," FastAPI(",[915,2281,2282],{"class":924},"responses",[915,2284,957],{"class":920},[915,2286,2287],{"class":928},"{",[915,2289,2261],{"class":932},[915,2291,1872],{"class":928},[915,2293,2294],{"class":994},"\"model\"",[915,2296,2297],{"class":928},": ErrorEnvelope}})\n",[675,2299,2301],{"id":2300},"verification","Verification",[906,2303,2305],{"className":908,"code":2304,"language":910,"meta":911,"style":911},"def test_validation_error_matches_the_contract(client):\n    r = client.post(\"\u002Forders\u002F7\", json={\"sku\": \"ab\", \"quantity\": 0, \"channel\": \"kiosk\"})\n    assert r.status_code == 422\n    body = r.json()\n    assert body[\"error\"][\"code\"] == \"validation_failed\"\n    fields = {f[\"field\"]: f for f in body[\"error\"][\"fields\"]}\n    assert fields[\"quantity\"][\"reason\"] == \"greater_than\"          # stable machine code\n    assert \"0\" in fields[\"quantity\"][\"message\"]                    # ctx rendered in\n\n\ndef test_handler_survives_unserialisable_input(client):\n    # A NaN float reaches exc.errors() as `input`; jsonable_encoder must cope.\n    r = client.post(\"\u002Forders\u002F7\", json={\"sku\": \"ok!\", \"quantity\": 1e999, \"channel\": \"web\"})\n    assert r.status_code == 422        # not a 500 from inside the handler\n",[604,2306,2307,2317,2369,2383,2392,2412,2446,2470,2494,2498,2502,2511,2516,2560],{"__ignoreMap":911},[915,2308,2309,2311,2314],{"class":764,"line":917},[915,2310,1375],{"class":920},[915,2312,2313],{"class":1021}," test_validation_error_matches_the_contract",[915,2315,2316],{"class":928},"(client):\n",[915,2318,2319,2322,2324,2327,2330,2332,2335,2337,2339,2342,2344,2347,2349,2352,2354,2356,2358,2361,2363,2366],{"class":764,"line":939},[915,2320,2321],{"class":928},"    r ",[915,2323,957],{"class":920},[915,2325,2326],{"class":928}," client.post(",[915,2328,2329],{"class":994},"\"\u002Forders\u002F7\"",[915,2331,621],{"class":928},[915,2333,2334],{"class":924},"json",[915,2336,957],{"class":920},[915,2338,2287],{"class":928},[915,2340,2341],{"class":994},"\"sku\"",[915,2343,1270],{"class":928},[915,2345,2346],{"class":994},"\"ab\"",[915,2348,621],{"class":928},[915,2350,2351],{"class":994},"\"quantity\"",[915,2353,1270],{"class":928},[915,2355,983],{"class":932},[915,2357,621],{"class":928},[915,2359,2360],{"class":994},"\"channel\"",[915,2362,1270],{"class":928},[915,2364,2365],{"class":994},"\"kiosk\"",[915,2367,2368],{"class":928},"})\n",[915,2370,2371,2374,2377,2380],{"class":764,"line":966},[915,2372,2373],{"class":920},"    assert",[915,2375,2376],{"class":928}," r.status_code ",[915,2378,2379],{"class":920},"==",[915,2381,2382],{"class":932}," 422\n",[915,2384,2385,2387,2389],{"class":764,"line":988},[915,2386,2109],{"class":928},[915,2388,957],{"class":920},[915,2390,2391],{"class":928}," r.json()\n",[915,2393,2394,2396,2399,2401,2403,2405,2407,2409],{"class":764,"line":1006},[915,2395,2373],{"class":920},[915,2397,2398],{"class":928}," body[",[915,2400,2116],{"class":994},[915,2402,1786],{"class":928},[915,2404,2121],{"class":994},[915,2406,1791],{"class":928},[915,2408,2379],{"class":920},[915,2410,2411],{"class":994}," \"validation_failed\"\n",[915,2413,2414,2417,2419,2422,2425,2428,2430,2433,2435,2437,2439,2441,2443],{"class":764,"line":1013},[915,2415,2416],{"class":928},"    fields ",[915,2418,957],{"class":920},[915,2420,2421],{"class":928}," {f[",[915,2423,2424],{"class":994},"\"field\"",[915,2426,2427],{"class":928},"]: f ",[915,2429,1417],{"class":920},[915,2431,2432],{"class":928}," f ",[915,2434,1423],{"class":920},[915,2436,2398],{"class":928},[915,2438,2116],{"class":994},[915,2440,1786],{"class":928},[915,2442,2149],{"class":994},[915,2444,2445],{"class":928},"]}\n",[915,2447,2448,2450,2453,2455,2457,2460,2462,2464,2467],{"class":764,"line":1018},[915,2449,2373],{"class":920},[915,2451,2452],{"class":928}," fields[",[915,2454,2351],{"class":994},[915,2456,1786],{"class":928},[915,2458,2459],{"class":994},"\"reason\"",[915,2461,1791],{"class":928},[915,2463,2379],{"class":920},[915,2465,2466],{"class":994}," \"greater_than\"",[915,2468,2469],{"class":1546},"          # stable machine code\n",[915,2471,2472,2474,2477,2480,2482,2484,2486,2488,2491],{"class":764,"line":1038},[915,2473,2373],{"class":920},[915,2475,2476],{"class":994}," \"0\"",[915,2478,2479],{"class":920}," in",[915,2481,2452],{"class":928},[915,2483,2351],{"class":994},[915,2485,1786],{"class":928},[915,2487,2127],{"class":994},[915,2489,2490],{"class":928},"]                    ",[915,2492,2493],{"class":1546},"# ctx rendered in\n",[915,2495,2496],{"class":764,"line":1053},[915,2497,1010],{"emptyLinePlaceholder":1009},[915,2499,2500],{"class":764,"line":1059},[915,2501,1010],{"emptyLinePlaceholder":1009},[915,2503,2504,2506,2509],{"class":764,"line":1070},[915,2505,1375],{"class":920},[915,2507,2508],{"class":1021}," test_handler_survives_unserialisable_input",[915,2510,2316],{"class":928},[915,2512,2513],{"class":764,"line":1090},[915,2514,2515],{"class":1546},"    # A NaN float reaches exc.errors() as `input`; jsonable_encoder must cope.\n",[915,2517,2518,2520,2522,2524,2526,2528,2530,2532,2534,2536,2538,2541,2543,2545,2547,2550,2552,2554,2556,2558],{"class":764,"line":1095},[915,2519,2321],{"class":928},[915,2521,957],{"class":920},[915,2523,2326],{"class":928},[915,2525,2329],{"class":994},[915,2527,621],{"class":928},[915,2529,2334],{"class":924},[915,2531,957],{"class":920},[915,2533,2287],{"class":928},[915,2535,2341],{"class":994},[915,2537,1270],{"class":928},[915,2539,2540],{"class":994},"\"ok!\"",[915,2542,621],{"class":928},[915,2544,2351],{"class":994},[915,2546,1270],{"class":928},[915,2548,2549],{"class":932},"1e999",[915,2551,621],{"class":928},[915,2553,2360],{"class":994},[915,2555,1270],{"class":928},[915,2557,995],{"class":994},[915,2559,2368],{"class":928},[915,2561,2562,2564,2566,2568,2571],{"class":764,"line":1343},[915,2563,2373],{"class":920},[915,2565,2376],{"class":928},[915,2567,2379],{"class":920},[915,2569,2570],{"class":932}," 422",[915,2572,2573],{"class":1546},"        # not a 500 from inside the handler\n",[590,2575,2576],{},"The second test is the one worth keeping. Serialisation failures inside an error handler are the classic way this pattern breaks in production, and they only show up with unusual input.",[675,2578,2580],{"id":2579},"trade-offs-and-when-not-to","Trade-Offs and When Not To",[590,2582,2583,2589,2590,2592],{},[593,2584,2585,2586,635],{},"Your OpenAPI schema still says 422 with ",[604,2587,2588],{},"HTTPValidationError"," FastAPI generates that from the route definitions, not from your handler, so consumers generating clients from the schema get the wrong shape. Fixing it means declaring your envelope model in ",[604,2591,2282],{}," at the app or router level — worth doing if clients are generated, skippable if they are not.",[590,2594,2595,2598,2599,2602,2603,2605,2606,2608],{},[593,2596,2597],{},"Error messages are a contract too."," Once a mobile client displays your ",[604,2600,2601],{},"message"," strings, changing them is a client-visible change. If localisation matters, send ",[604,2604,1972],{}," and ",[604,2607,634],{}," and let the client render the text; sending prose is a decision to own the wording forever.",[590,2610,2611,2617,2618,2620,2621,2623],{},[593,2612,2613,2614,2616],{},"Do not leak ",[604,2615,630],{}," to untrusted clients."," The raw error list echoes what the caller sent — including a mistyped password in a login body — straight into your response and, if you log it, into your logs. Gate the ",[604,2619,660],{}," block on environment, and consider stripping ",[604,2622,630],{}," entirely.",[590,2625,2626,2629,2630,2633,2634,2637,2638,2640,2641,635],{},[593,2627,2628],{},"This handler covers inbound validation only."," A response that fails its ",[604,2631,2632],{},"response_model"," raises ",[604,2635,2636],{},"ResponseValidationError",", which is a bug in your code and correctly produces a ",[604,2639,1961],{},". Wrapping it in a friendly envelope would hide a real fault; the ordering of that check is covered in ",[669,2642,2644],{"href":2643},"\u002Fcore-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fresponse-model-and-serialization-order\u002F","response model and serialization order",[590,2646,2647,2650,2651,2654],{},[593,2648,2649],{},"A very large error list is a denial-of-service surface."," A deeply nested model posted with a thousand bad entries produces a thousand error dicts, each embedding its input. Truncate ",[604,2652,2653],{},"fields"," to a sane maximum and note the total.",[675,2656,2658],{"id":2657},"faq","FAQ",[590,2660,2661,2664,2665,865,2667,607,2670,2672],{},[593,2662,2663],{},"How do I change the default 422 response body in FastAPI?","\nRegister an exception handler for ",[604,2666,759],{},[604,2668,2669],{},"app.exception_handler",[604,2671,610],{},". FastAPI installs a default handler for that exception at startup and yours replaces it for the whole application.",[590,2674,2675,2678],{},[593,2676,2677],{},"Should I change the 422 status code to 400?","\nYou can, by setting the status code in your handler, and some API contracts require it. The cost is that your OpenAPI schema still documents 422 for every operation unless you also override the generated responses, so clients generated from the schema will be wrong.",[590,2680,2681,2684,2685,2687,2688,621,2690,2692,2693,2695,2696,635],{},[593,2682,2683],{},"How do I know which field failed when loc has several entries?","\nThe first element of ",[604,2686,624],{}," is the location, such as ",[604,2689,644],{},[604,2691,647],{}," or ",[604,2694,650],{},", and the rest is the path to the field. Dropping the first element and joining the remainder with dots gives a client-friendly field name like ",[604,2697,2698],{},"items.0.price",[590,2700,2701,2704,2705,2707,2708,2710],{},[593,2702,2703],{},"Can I keep the original Pydantic errors for debugging?","\nYes. ",[604,2706,616],{}," returns the full list of dictionaries and you can embed it in a debug section of your envelope, or log it and return only the friendly messages. Run it through ",[604,2709,1932],{}," first because inputs may not be JSON-serialisable.",[590,2712,2713,2716,2717,2633,2719,2721,2722,2724,2725,635],{},[593,2714,2715],{},"Does this handler also catch validation errors in my response model?","\nNo. A response that fails its ",[604,2718,2632],{},[604,2720,2636],{},", which is a server-side fault and produces a ",[604,2723,1961],{},". Only inbound request validation raises ",[604,2726,759],{},[590,2728,2729,2732,2733,2735,2736,2738,2739,2741],{},[593,2730,2731],{},"Why does my handler return a 500 instead of my envelope?","\nAlmost always a serialisation failure inside the handler itself, because ",[604,2734,616],{}," contains an ",[604,2737,630],{}," value that is not JSON-serialisable. Wrap the content in ",[604,2740,1932],{}," and add a test that posts an exotic value.",[675,2743,2745],{"id":2744},"related-reading","Related Reading",[597,2747,2748,2756,2767,2775,2784],{},[600,2749,2750,1934,2753,635],{},[593,2751,2752],{},"Up to the section:",[669,2754,2755],{"href":671},"Error Handling and Global Exceptions",[600,2757,2758,1934,2761,2605,2764,635],{},[593,2759,2760],{},"One envelope for every failure:",[669,2762,485],{"href":2763},"\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fglobal-exception-handlers-for-consistent-api-responses\u002F",[669,2765,2766],{"href":2246},"HTTPException vs Custom Exception Classes",[600,2768,2769,1934,2772,635],{},[593,2770,2771],{},"Where the handler sits:",[669,2773,2774],{"href":887},"Middleware Execution Order",[600,2776,2777,1934,2780,635],{},[593,2778,2779],{},"What produces these errors:",[669,2781,2783],{"href":2782},"\u002Fadvanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Fquery-path-and-body-parameter-validation\u002F","Query, Path and Body Parameter Validation",[600,2785,2786,1934,2789,635],{},[593,2787,2788],{},"The outbound counterpart:",[669,2790,569],{"href":2643},[2792,2793,2794],"style",{},"html pre.shiki code .sTJeM, html code.shiki .sTJeM{--shiki-default:#A0111F}html pre.shiki code .sV4o_, html code.shiki .sV4o_{--shiki-default:#702C00}html pre.shiki code .sigWx, html code.shiki .sigWx{--shiki-default:#0E1116}html pre.shiki code .sacAq, html code.shiki .sacAq{--shiki-default:#023B95}html pre.shiki code .sYEJz, html code.shiki .sYEJz{--shiki-default:#032563}html pre.shiki code .s3dhs, html code.shiki .s3dhs{--shiki-default:#622CBC}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sFeEa, html code.shiki .sFeEa{--shiki-default:#66707B}",{"title":911,"searchDepth":939,"depth":939,"links":2796},[2797,2798,2799,2800,2801,2802,2803,2804,2805,2806],{"id":677,"depth":939,"text":678},{"id":842,"depth":939,"text":843},{"id":897,"depth":939,"text":898},{"id":1178,"depth":939,"text":1179},{"id":2004,"depth":939,"text":2005},{"id":2044,"depth":939,"text":2045},{"id":2300,"depth":939,"text":2301},{"id":2579,"depth":939,"text":2580},{"id":2657,"depth":939,"text":2658},{"id":2744,"depth":939,"text":2745},"2026-07-20","Override RequestValidationError to return your own error envelope, keep the raw Pydantic detail for debugging, and match an existing API error contract.","md",[2811,2813,2815,2817,2819],{"q":2663,"a":2812},"Register an exception handler for RequestValidationError with app.exception_handler and return your own JSONResponse. FastAPI installs a default handler for that exception at startup and yours replaces it for the whole application.",{"q":2677,"a":2814},"You can, by setting the status code in your handler, and some API contracts require it. The cost is that your OpenAPI schema still documents 422 for every operation unless you also override the generated responses, so clients generated from the schema will be wrong.",{"q":2683,"a":2816},"The first element of loc is the location, such as body, query or path, and the rest is the path to the field. Dropping the first element and joining the remainder with dots gives a client-friendly field name like items.0.price.",{"q":2703,"a":2818},"Yes. exc.errors() returns the full list of dictionaries and you can embed it in a debug section of your envelope, or log it and return only the friendly messages. Run it through jsonable_encoder first because inputs may not be JSON-serialisable.",{"q":2715,"a":2820},"No. A response that fails its response_model raises ResponseValidationError, which is a server-side fault and produces a 500. Only inbound request validation raises RequestValidationError.",null,{"slug":2823,"breadcrumb":2824},"customising-validation-error-responses",[2825,2828,2831,2833],{"label":2826,"path":2827},"Home","\u002F",{"label":2829,"path":2830},"Core Architecture & Routing Patterns","\u002Fcore-architecture-routing-patterns\u002F",{"label":2832,"path":671},"Error Handling & Global Exceptions",{"label":2834,"path":2835},"Customising Validation Error Responses","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fcustomising-validation-error-responses\u002F",{"title":479,"description":2808},"article","aJp2ehPVavyrLF8eFj6TTyNS1ImiegBBKWiG6qB5FXo",[2821,2821],1784588202620]