Skip to content

API Design Principles

Versioning

All routes are prefixed with /api/v1/. Include the version in the router:

router = APIRouter(prefix="/api/v1")

Standardized Response Envelope

For list endpoints, use a consistent pagination wrapper:

from typing import Generic, TypeVar
from math import ceil

T = TypeVar("T")


class Page(BaseModel, Generic[T]):
    items: list[T]
    total: int
    page: int
    size: int
    pages: int

    @classmethod
    def create(cls, items: list[T], total: int, page: int, size: int) -> "Page[T]":
        return cls(items=items, total=total, page=page, size=size, pages=ceil(total / size))

Consistent Interfaces

  • POST returns 201 with the created resource
  • GET returns 200 with the resource or list
  • PATCH returns 200 with the updated resource
  • DELETE returns 204 with no body
  • Error responses always return {"error": {"code": ..., "message": ..., "request_id": ...}}

Naming Conventions

Resource Endpoint Method
List /api/v1/users GET
Create /api/v1/users POST
Get /api/v1/users/{user_id} GET
Update /api/v1/users/{user_id} PATCH
Delete /api/v1/users/{user_id} DELETE
Action /api/v1/users/{user_id}/deactivate POST

See Also

  • Error Contracts -- Standardized error response format and status codes
  • API Versioning -- Strategies for evolving the API without breaking clients
  • OpenAPI -- Automatic documentation and schema generation with FastAPI