Teams treat a 422 Unprocessable Entity like an embarrassment — something to
soften into a vague 400 with "Invalid request". That hides the
whole point of a schema. When Pydantic rejects a payload, it already knows which field
failed, why, and what was expected. That detail is an API feature, not a leak.
The happy path is only half the contract
OpenAPI documents the request body you hope to receive. Clients also need to know how you
reject the rest. Structured errors let a UI highlight email, show that
age must be an integer, and retry without a support ticket that says “it
didn’t work.”
FastAPI’s default validation response is already useful: a list of errors with
loc, msg, and type. Before you replace it, ask
whether you are improving clarity for clients — or just making the server feel quieter.
What a useful error looks like
{
"detail": [
{
"type": "int_parsing",
"loc": ["body", "age"],
"msg": "Input should be a valid integer, unable to parse string as an integer",
"input": "thirty"
}
]
}
A human can map that to a form field. A machine can branch on type and
loc. A single string "Bad request" forces every client to guess.
Don’t conflate validation with domain rules
Schema failures belong at the edge: wrong types, missing required fields, malformed
emails. Domain failures — “email already registered,” “insufficient balance” — are
different. They usually deserve their own status codes and payloads, not a stretched
ValidationError.
Mixing them produces mush: either every business rule becomes a custom validator nobody can reuse, or every schema miss becomes a generic application exception with no field path. Keep parse errors and business errors on separate tracks.
Customize when you have a reason
Stable error envelopes, redaction of sensitive input values, or translating
messages for product copy are good reasons to wrap the default handler. Cosmetic silence
is not. If you strip loc because “clients shouldn’t see internals,” remember
those locations are the public field names you already published in the schema.
Log the structured error server-side. Return enough for the caller to fix the payload. That is the same discipline as runtime validation itself: fail clearly, at the boundary, with a shape both sides can rely on.
Bad data will arrive. Your API’s job is to say exactly how it failed — not that it felt awkward about saying so.