M-Bus Gateway
← Tilbage til blog
· OpenAPI· FastAPI· API· dokumentation· Python· Swagger· ReDoc· integration

OpenAPI dokumentation med FastAPI — komplet guide

FastAPI OpenAPI dokumentation: custom schemas, response eksempler, tags, security schemes, versioning, ReDoc vs Swagger UI og eksport til tredjeparts-integration.

Af M-Bus Gateway

FastAPI genererer automatisk OpenAPI 3.1 dokumentation. Her er hvordan du tilpasser den til professionel API-dokumentation.


App-konfiguration

# server/src/main.py

from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi

app = FastAPI(
    title="M-Bus Gateway API",
    version="1.0.0",
    description="""
## M-Bus Gateway Platform API

Automatisk forbrugsregistrering og fordelingsregnskab for udlejningsejendomme.

### Autentificering

Alle endpoints kræver JWT Bearer token — eksempel:
    Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...

Token hentes via `POST /api/v1/auth/login`.

### Rate limiting

- 100 requests/60s pr. IP (public endpoints)
- 1000 requests/60s pr. authenticated bruger

### Versioning

Alle endpoints starter med `/api/v1/`. Breaking changes introducerer `/api/v2/`.
    """,
    contact={
        "name": "M-Bus Gateway Support",
        "email": "support@mbus-gateway.dk",
    },
    license_info={
        "name": "Proprietary",
    },
    docs_url="/api/docs",
    redoc_url="/api/redoc",
    openapi_url="/api/openapi.json",
)

Tags og gruppering

# server/src/api/tags.py

tags_metadata = [
    {
        "name": "auth",
        "description": "Autentificering, token refresh og 2FA.",
    },
    {
        "name": "properties",
        "description": "Ejendomme — CRUD og konfiguration.",
    },
    {
        "name": "units",
        "description": "Lejligheder og enheder inden for ejendomme.",
    },
    {
        "name": "readings",
        "description": "Forbrugsaflæsninger fra wM-Bus målere.",
    },
    {
        "name": "settlements",
        "description": "Årsafregninger — generering, PDF-eksport og udsendelse.",
    },
    {
        "name": "portfolio",
        "description": "Portefølje-analyser og dashboards (66 KPI-endpoints).",
        "externalDocs": {
            "description": "Portfolio API reference",
            "url": "https://docs.mbus-gateway.dk/portfolio",
        },
    },
]

app = FastAPI(..., openapi_tags=tags_metadata)

Endpoint-dokumentation

# server/src/properties/router.py

from fastapi import APIRouter, Depends, HTTPException, status
from typing import Annotated

router = APIRouter(prefix="/properties", tags=["properties"])

@router.get(
    "/",
    summary="List ejendomme",
    description="""
Returnerer pagineret liste over alle ejendomme for den autentificerede udlejer.

**Filtrering:**
- `search`: Fritekst-søgning i navn og adresse
- `city`: Filtrer på by
- `status`: `active` | `archived`

**Sortering:**
- `sort`: `name` | `created_at` | `unit_count` (prefix `-` for faldende)
    """,
    response_description="Pagineret liste af ejendomme",
    responses={
        200: {
            "description": "Succes",
            "content": {
                "application/json": {
                    "example": {
                        "items": [
                            {
                                "id": "550e8400-e29b-41d4-a716-446655440000",
                                "name": "Eksempelgården",
                                "address": "Eksempelvej 1-3",
                                "zip_code": "2100",
                                "city": "København Ø",
                                "unit_count": 12,
                                "active_gateways": 1,
                            }
                        ],
                        "total": 42,
                        "page": 1,
                        "per_page": 20,
                    }
                }
            },
        },
        401: {"description": "Ikke autentificeret"},
        403: {"description": "Manglende adgang"},
    },
)
async def list_properties(
    search: str | None = None,
    city: str | None = None,
    status: str = "active",
    sort: str = "name",
    page: int = 1,
    per_page: Annotated[int, Query(le=100)] = 20,
    user: TokenPayload = Depends(get_current_user),
    session: AsyncSession = Depends(get_session),
):
    ...

Custom response schemas

# server/src/schemas/common.py

from pydantic import BaseModel, Field
from typing import Generic, TypeVar

T = TypeVar("T")

class PaginatedResponse(BaseModel, Generic[T]):
    """Standard pagineringsrespons — bruges på alle list-endpoints."""
    items: list[T] = Field(description="Liste af resultater på denne side")
    total: int = Field(description="Samlet antal resultater")
    page: int = Field(description="Nuværende sidenummer (1-baseret)")
    per_page: int = Field(description="Resultater pr. side")

    model_config = {
        "json_schema_extra": {
            "example": {
                "items": [],
                "total": 0,
                "page": 1,
                "per_page": 20,
            }
        }
    }

class ErrorResponse(BaseModel):
    """Standard fejl-respons."""
    detail: str = Field(description="Fejlbeskrivelse")
    code: str | None = Field(None, description="Maskinlæsbar fejlkode")

    model_config = {
        "json_schema_extra": {
            "example": {
                "detail": "Ejendom ikke fundet",
                "code": "property_not_found",
            }
        }
    }

Security scheme

# server/src/main.py

from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials

security = HTTPBearer(
    scheme_name="JWT Bearer",
    description="JWT access token fra POST /api/v1/auth/login",
    auto_error=False,
)

def custom_openapi():
    if app.openapi_schema:
        return app.openapi_schema

    openapi_schema = get_openapi(
        title=app.title,
        version=app.version,
        description=app.description,
        routes=app.routes,
        tags=tags_metadata,
    )

    # Tilføj JWT security scheme
    openapi_schema["components"]["securitySchemes"] = {
        "BearerAuth": {
            "type": "http",
            "scheme": "bearer",
            "bearerFormat": "JWT",
            "description": "Indsæt JWT token fra /auth/login",
        }
    }

    # Anvend security globalt
    openapi_schema["security"] = [{"BearerAuth": []}]

    app.openapi_schema = openapi_schema
    return app.openapi_schema

app.openapi = custom_openapi

Eksport og tredjeparts-integration

# Eksportér OpenAPI JSON-spec:
curl https://api.mbus-gateway.dk/api/openapi.json > openapi.json

# Generer TypeScript-klient (openapi-typescript):
npx openapi-typescript openapi.json --output ui/src/generated/api.ts

# Generer Python-klient (openapi-python-client):
openapi-python-client generate --url https://api.mbus-gateway.dk/api/openapi.json

# Valider spec (spectral):
npx @stoplight/spectral-cli lint openapi.json --ruleset .spectral.yml
// ui/src/generated/api.ts (auto-genereret)
// Aldrig rediger denne fil manuelt — regenerér fra spec

export interface PropertyOut {
  id: string;
  name: string;
  address: string;
  zip_code: string;
  city: string;
  unit_count: number;
  active_gateways: number;
}

export interface PaginatedPropertyOut {
  items: PropertyOut[];
  total: number;
  page: number;
  per_page: number;
}

Webhook-dokumentation

# Dokumentér webhooks i OpenAPI 3.1 (callbacks):

@router.post(
    "/webhooks/stripe",
    include_in_schema=False,  # Skjul fra Swagger UI
)
async def stripe_webhook(...): ...

# Alternativt: beskriv webhooks separat i description:
WEBHOOK_DOCS = """
## Webhooks

M-Bus Gateway sender webhooks til din endpoint ved følgende events:

| Event | Trigger |
|-------|---------|
| `reading.received` | Ny aflæsning modtaget fra gateway |
| `alarm.triggered` | Alarm udløst (RSSI, batteri, stillestående måler) |
| `settlement.generated` | Årsafregning genereret klar til afsendelse |
| `settlement.sent` | Afregning sendt til lejer |
| `gateway.offline` | Gateway ikke set i 36+ timer |

Payload er signeret med HMAC-SHA256 (header: `X-Mbus-Signature`).
"""

Konklusion

FastAPI's automatiske OpenAPI-generering reducerer dokumentationsarbejde til nul for simple endpoints. Custom tags, eksempler og security schemes giver professionel API-dokumentation. Eksport til openapi.json muliggør auto-genererede TypeScript/Python-klienter — frontenden bruger auto-genererede typer, aldrig håndskrevne.

Se FastAPI multi-tenant guide eller API-nøgler guide.