(+351) 21 24 10006  ·  info@bconcepts.pt
Carnaxide, Lisbon

How to Version a Data API with FastAPI and OpenAPI

João Barros 20 de July de 2026 4 min read

This tutorial shows how to version a Data API with FastAPI and OpenAPI in a practical and useful way. Knowing how to version an API allows introducing changes without breaking clients, documenting differences and managing migrations with less risk.

Prerequisites

  • Python 3.9+ and pip installed
  • Basic knowledge of FastAPI and HTTP
  • Code editor and terminal

Step 1: prepare the environment

Create a virtual environment and install FastAPI and Uvicorn. We use minimal dependencies to focus on versioning.

python -m venv .venv
source .venv/bin/activate  # ou .venv\Scripts\activate no Windows
pip install fastapi uvicorn

Step 2: create routers separated by version (path versioning)

A simple and explicit approach is to use prefixes like /v1 and /v2. Each router has its own implementation, allowing internal changes without cross-version impact.

# app/main.py
from fastapi import FastAPI
from .routers import v1, v2

app = FastAPI(title="API de Dados - Exemplo")
app.include_router(v1.router, prefix="/v1", tags=["v1"])
app.include_router(v2.router, prefix="/v2", tags=["v2"]) 

# routers/v1.py
from fastapi import APIRouter

router = APIRouter()

@router.get("/items")
def list_items():
    # resposta simples v1
    return {"version": "v1", "items": ["a", "b"]}

# routers/v2.py
from fastapi import APIRouter

router = APIRouter()

@router.get("/items")
def list_items():
    # resposta diferente em v2 (exemplo: items com id)
    return {"version": "v2", "items": [{"id":1, "name":"a"}, {"id":2, "name":"b"}]}

Step 3: version negotiation via header (header versioning)

Some teams prefer to negotiate the version with a header to keep URLs stable. Here we show a simple middleware that reroutes to the appropriate router using a custom Accept-Version header.

# app/version_middleware.py
from starlette.middleware.base import BaseHTTPMiddleware
from fastapi import Request
from fastapi.responses import JSONResponse

class VersionRoutingMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        version = request.headers.get("Accept-Version")
        # se existir cabeçalho, prefixa a path com /v{n}
        if version and not request.url.path.startswith(f"/v{version}"):
            # reescreve o scope para simular um pedido a /v{version}/... 
            new_path = f"/v{version}{request.url.path}"
            request.scope["path"] = new_path
            request.scope["raw_path"] = new_path.encode()
        return await call_next(request)

# registar no main.py
from .version_middleware import VersionRoutingMiddleware
app.add_middleware(VersionRoutingMiddleware)

Step 4: document differences with OpenAPI and metadata

FastAPI generates OpenAPI automatically. Use description, tags and operationId to clarify differences between versions and add deprecated when necessary.

# exemplo de endpoint com deprecação
@router.get("/items", deprecated=True, summary="Listar items (v1 - obsoleto)")
def list_items():
    return {"version": "v1", "items": []}

# no FastAPI, a documentação está em /docs (Swagger UI) e /redoc

Step 5: strategies for compatibility and migration

Define internal rules: what is a breaking change? Contract changes (fields, types) are breaking. To minimize impact:

  • Prefer adding fields instead of removing them
  • Mark obsolete endpoints with deprecated and keep them for a period
  • Document differences clearly in the OpenAPI and changelogs
  • If needed, provide an informative header (e.g.: X-Deprecation-Date) to warn clients

Verify the result

Start the application with Uvicorn and test both types of versioning.

uvicorn app.main:app --reload

# path versioning
curl http://127.0.0.1:8000/v1/items
curl http://127.0.0.1:8000/v2/items

# header versioning (aplicando middleware)
curl -H "Accept-Version: 2" http://127.0.0.1:8000/items

# abrir documentação
# https://127.0.0.1:8000/docs  (Swagger UI)

Check that the output differs according to the version and that /docs shows separate tags for v1 and v2. Test deprecation by observing the deprecated field on endpoints.

Conclusion

Versioning a Data API with FastAPI and OpenAPI allows managing changes in a controlled way: use path versioning for clarity and header versioning for stable URLs, document in OpenAPI and mark obsolete endpoints. Next step: add automated tests that validate compatibility between versions (contract tests). Tip: start by defining a versioning policy (semver or just major) before publishing the first version.