Drive the whole OpenAPI contract with Annotated metadata in FastAPI and Pydantic

Today I Learned · August 2, 2026

Drive the whole OpenAPI contract with Annotated metadata in FastAPI and Pydantic

FastAPI and Pydantic both read Annotated metadata, merge it into the OpenAPI spec, and Orval turns that spec into a complete typed client. Write the contract once; get validation on the backend and a full client on the frontend for free.

Where metadata lives

Query/path params and body fields all accept Annotated metadata:

from typing import Annotated
from fastapi import Query

@router.get("/items")
def list_items(
    page: Annotated[int, Query(ge=1, le=100)] = 1,
    limit: Annotated[int, Query(ge=1, le=50)] = 20,
):
    ...

Pydantic model fields use the same trick:

from typing import Annotated
from pydantic import BaseModel, Field

class Item(BaseModel):
    name: Annotated[str, Field(min_length=1, max_length=120)]
    price: Annotated[float, Field(gt=0)]

Why it matters

Both frameworks pick up that metadata and emit it into the OpenAPI schema. Orval consumes the spec and generates a client that carries:

The contract lives in one place (your Python code). The frontend client, its validation, and the docs all derive from it, so they can’t drift.

Gotchas