RFC-012: API Design Standards e Versionamento
| Campo | Valor |
|---|---|
| Status | Draft |
| Author | Time de Tecnologia |
| Created | 2026-02-05 |
| Updated | 2026-02-05 |
| Reunião | A ser agendada |
| Deciders | Todo o time técnico |
Status
🟡 Draft - RFC em elaboração, aguardando apresentação
Contexto e Problema
REST APIs documentadas em docs/architecture/api-design.md, mas falta:
- Versionamento strategy formal (URL vs header vs query param)
- Deprecation policy com timelines claros
- Breaking changes process definido
- OpenAPI spec enforcement no CI/CD
- Backwards compatibility requirements
Por que resolver isso agora?
- APIs crescendo, necessidade de versionar
- Breaking changes não têm processo claro
- Clientes externos começando a usar APIs
- Falta documentação automática (OpenAPI)
- Necessidade de deprecar endpoints antigos
Impacto de não resolver
- Breaking changes quebram clientes
- APIs crescem desorganizadas
- Dificuldade em deprecar código legado
- Documentação desatualizada
- Clientes frustrados com mudanças inesperadas
Documentação relacionada:
- docs/architecture/api-design.md - Design atual REST APIs
- docs/api-reference/swagger-ui.md - Swagger UI
- docs/api-reference/index.md - Referência APIs
- docs/architecture/overview.md - Arquitetura geral
Proposta de Solução
API design standards com URL versioning, deprecation policy de 6 meses, e OpenAPI automático.
Versionamento Strategy
URL Path Versioning (escolhido):
Quando criar nova versão: - ✅ Breaking change em request/response format - ✅ Remover campos obrigatórios - ✅ Mudar tipos de dados - ✅ Mudar comportamento significativo - ❌ Adicionar campos opcionais (backward compatible) - ❌ Adicionar novos endpoints - ❌ Bug fixes
Versão atual: v1
URL Structure
# Recursos
GET /api/v1/users # List
POST /api/v1/users # Create
GET /api/v1/users/{id} # Get
PUT /api/v1/users/{id} # Update (full)
PATCH /api/v1/users/{id} # Update (partial)
DELETE /api/v1/users/{id} # Delete
# Sub-recursos
GET /api/v1/users/{id}/orders # List user orders
POST /api/v1/users/{id}/orders # Create order
# Ações (quando REST não se aplica bem)
POST /api/v1/users/{id}/reset-password
POST /api/v1/orders/{id}/cancel
POST /api/v1/orders/{id}/refund
# Queries
GET /api/v1/users?status=active&role=admin&page=1&page_size=20
Request/Response Standards
Request (POST/PUT/PATCH):
Response Success (200/201):
{
"id": 123,
"email": "user@example.com",
"name": "John Doe",
"age": 30,
"created_at": "2026-01-20T10:00:00Z",
"updated_at": "2026-01-20T10:00:00Z"
}
Response Error (4xx/5xx):
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input data",
"details": [
{
"field": "email",
"message": "Invalid email format",
"code": "INVALID_FORMAT"
}
],
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}
Pagination
Request:
Response:
{
"data": [...],
"pagination": {
"page": 2,
"page_size": 20,
"total_items": 150,
"total_pages": 8,
"has_next": true,
"has_prev": true,
"links": {
"next": "/api/v1/users?page=3&page_size=20",
"prev": "/api/v1/users?page=1&page_size=20",
"first": "/api/v1/users?page=1&page_size=20",
"last": "/api/v1/users?page=8&page_size=20"
}
}
}
OpenAPI Specification
FastAPI (automático):
from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI(
title="People API",
description="API for People management system",
version="1.0.0",
docs_url="/api/v1/docs", # Swagger UI
redoc_url="/api/v1/redoc", # ReDoc
openapi_url="/api/v1/openapi.json"
)
class UserCreate(BaseModel):
"""User creation request."""
email: str = Field(..., example="user@example.com", description="User email")
name: str = Field(..., min_length=2, max_length=100, example="John Doe")
age: int | None = Field(None, ge=0, le=150, example=30)
model_config = {
"json_schema_extra": {
"example": {
"email": "john@example.com",
"name": "John Doe",
"age": 30
}
}
}
@app.post(
"/api/v1/users",
response_model=UserResponse,
status_code=201,
summary="Create a new user",
description="Creates a new user with the provided data",
response_description="The created user",
tags=["Users"]
)
async def create_user(user: UserCreate):
"""
Create a new user.
- **email**: must be valid email format
- **name**: 2-100 characters
- **age**: optional, 0-150
"""
...
Django REST Framework:
from rest_framework import serializers, viewsets
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
class UserSerializer(serializers.ModelSerializer):
"""User serializer for API."""
class Meta:
model = User
fields = ['id', 'email', 'name', 'age', 'created_at']
read_only_fields = ['id', 'created_at']
class UserViewSet(viewsets.ModelViewSet):
"""User management endpoints."""
queryset = User.objects.all()
serializer_class = UserSerializer
@swagger_auto_schema(
operation_description="Create a new user",
responses={
201: UserSerializer,
400: "Validation error"
}
)
def create(self, request):
...
OpenAPI Spec Export:
# FastAPI (built-in)
curl http://localhost:8000/api/v1/openapi.json > openapi.json
# Django REST Framework
python manage.py spectacular --file openapi.json
# CI/CD: auto-publish to docs site
cp openapi.json docs/api-reference/openapi.json
Deprecation Policy
Timeline: 6 meses
Processo:
- Announce (T-6 months): Comunicar deprecation
- Blog post
- Email para clients
-
Header em responses:
X-API-Deprecation: version=v1, sunset=2026-08-01 -
Warn (T-3 months): Avisos mais agressivos
- Warning em response body
-
Emails semanais para clients usando versão antiga
-
Sunset (T-0): Desligar versão antiga
- Retornar 410 Gone
- Redirecionar para documentação de migração
Response durante deprecation:
{
"data": {...},
"warnings": [
{
"code": "DEPRECATED_API_VERSION",
"message": "API v1 will be deprecated on 2026-08-01. Please migrate to v2.",
"migration_guide": "https://docs.seuapp.com/migration/v1-to-v2"
}
]
}
Headers:
X-API-Deprecation: version=v1, sunset=2026-08-01
X-API-Current-Version: v2
Link: <https://docs.seuapp.com/migration/v1-to-v2>; rel="migration-guide"
Breaking Changes
Exemplos de breaking changes: - Remover endpoint - Remover campo de response - Mudar tipo de campo (string → int) - Tornar campo opcional → obrigatório - Mudar formato de data - Mudar status codes
Processo para breaking change:
- RFC obrigatória para breaking changes
- Nova versão da API (/api/v2/)
- Manter versão antiga por 6 meses mínimo
- Migration guide detalhado
- Deprecation warnings conforme policy
NON-breaking changes (ok fazer em mesma versão): - Adicionar endpoint novo - Adicionar campo opcional em request - Adicionar campo em response - Adicionar valor enum - Bug fixes
Rate Limiting
# Por IP
@limiter.limit("100/hour")
# Por usuário autenticado
@limiter.limit("1000/hour", key_func=lambda: g.user.id)
# Por endpoint crítico
@router.post("/login")
@limiter.limit("5/minute") # Anti-brute force
# Response headers
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 73
X-RateLimit-Reset: 1643720400
Idempotency
Idempotency-Key header:
@router.post("/orders")
async def create_order(
request: CreateOrderRequest,
idempotency_key: str = Header(...)
):
# Check if already processed
existing = await get_order_by_idempotency_key(idempotency_key)
if existing:
return existing # Return cached result
# Process and cache
order = await create_order_internal(request)
await cache_idempotency_result(idempotency_key, order, ttl=86400)
return order
HATEOAS (Optional)
{
"id": 123,
"name": "John Doe",
"links": {
"self": "/api/v1/users/123",
"orders": "/api/v1/users/123/orders",
"update": "/api/v1/users/123",
"delete": "/api/v1/users/123"
}
}
Alternativas Consideradas
Opção 1: Header Versioning
Prós: - URLs mais limpos - RESTful "puro"
Contras: - Menos visível - Difícil testar (Postman, curl) - Caching mais complexo
Por que não escolhemos: URL versioning é mais explícito e fácil de usar.
Opção 2: Query Parameter Versioning
Prós: - Simples - Não muda URL base
Contras: - Não semântico - Fácil esquecer - Mixing concerns
Por que não escolhemos: Menos claro que URL versioning.
Opção 3: GraphQL ao invés de REST
Prós: - Versionamento não necessário - Clientes pedem apenas o que precisam - Strongly typed
Contras: - Curva de aprendizado alta - Complexidade adicional - Caching mais difícil - Over-fetching prevention requer disciplina
Por que não escolhemos: REST é suficiente e time já conhece.
Opção 4: Deprecation 3 meses (ao invés de 6)
Prós: - Move mais rápido - Remove código legado mais cedo
Contras: - Pouco tempo para clients migrarem - Frustração de clients - Risco de churn
Por que não escolhemos: 6 meses é padrão da indústria e respeitoso com clients.
Análise de Impacto
Impacto Técnico
- URLs versionadas: /api/v1/, /api/v2/
- OpenAPI spec gerada automaticamente
- Múltiplas versões coexistem
- Deprecation warnings automáticos
Impacto em Negócio
- ✅ APIs podem evoluir sem quebrar clients
- ✅ Documentação sempre atualizada
- ✅ Clients têm tempo para migrar
- ✅ Developer experience melhor
- ⚠️ Manutenção de múltiplas versões (overhead)
Riscos
Risco: Múltiplas versões aumentam complexidade
Mitigação: Máximo 2 versões ativas simultaneamente. Deprecation rigorosa.
Plano de Implementação
Fase 1: Standards (Semana 1)
- Documentar URL structure standards
- Criar templates de Pydantic models
- Documentar error response format
Fase 2: OpenAPI (Semana 2)
- Configurar OpenAPI export automático
- Publicar Swagger UI
- CI/CD valida OpenAPI spec
Fase 3: Versioning (Semana 3)
- Implementar /api/v1/ prefix
- Documentar deprecation policy
- Criar migration guide template
Fase 4: Deprecation (Mês 2+)
- Implementar deprecation warnings
- Process para comunicar deprecations
- Primeiro cycle de deprecation (futuro)
Métricas de Sucesso
Após 1 mês: - ✅ 100% endpoints em /api/v1/ - ✅ OpenAPI spec publicado e atualizado - ✅ Swagger UI acessível
Após 3 meses: - ✅ Zero breaking changes sem nova versão - ✅ Documentação API 100% atualizada - ✅ Feedback positivo de clients