Skip to content

Media type

media_type sets the content type of the validation error, both in the response header and in the schema. Combined with an error builder, it returns RFC 9457 Problem Details:

class Problem(BaseModel):
    type: str = "about:blank"
    title: str
    status: int
    detail: str


def build_problem(exc: RequestValidationError) -> Problem:
    return Problem(
        title="Validation failed",
        status=400,
        detail=f"{len(exc.errors())} invalid field(s)",
    )


override_validation_error(
    app, error_builder=build_problem, media_type="application/problem+json"
)

A request that fails validation gets 400 with content-type: application/problem+json:

{
  "type": "about:blank",
  "title": "Validation failed",
  "status": 400,
  "detail": "1 invalid field(s)"
}

and the schema documents the 400 under the application/problem+json key.

media_type must be application/json or a content type ending in +json, since the body is always JSON; anything else raises ValueError. It also works without error_builder, with the default {"detail": [...]} body.

Another error at the same code

A response the route already declares at the target code under another content type is kept next to the validation error, not merged with it:

@app.post(
    "/orders",
    responses={400: {"model": OutOfStock, "description": "Out of stock"}},
)
def create_order(item: Item) -> Item:
    return item
Content type Model
application/json OutOfStock
application/problem+json Problem

Swagger UI and ReDoc show the two under the media type menu of the 400 response. Under the same content type, the two models are merged with anyOf instead.

With handle_exceptions=False

Only the schema uses media_type: your own handler has to send the same content type.

With status_code=422

status_code=422 with a media_type other than application/json is not a no-op: it keeps the 422 and changes its content type.