Ir al contenido

F05 — Data & Contracts

AI-First Engineering Framework --- Baseline v6.5

Sección titulada «AI-First Engineering Framework --- Baseline v6.5»

Version: 1.0.0
Estado: Activo
Fecha: Marzo 2026
Autor: Engineering Team


Definir los estandares para disenar, documentar, versionar y mantener APIs en proyectos AI-Driven. Cubre REST APIs, API Contracts (OpenAPI), versionado, paginacion, error handling y patrones especificos para endpoints que integran IA.

“Una API bien disenada es la interfaz entre el dominio de negocio y cualquier consumidor --- humano, frontend o agente IA.”


principles:
- name: "Contract-First"
description: "El contrato OpenAPI se escribe ANTES del codigo"
benefit: "Frontend y backend pueden trabajar en paralelo"
- name: "RESTful by Default"
description: "Seguir constraints REST (stateless, uniform interface, HATEOAS)"
exception: "GraphQL para frontends con queries complejas y variables"
- name: "Consistent & Predictable"
description: "Misma estructura en todos los endpoints"
benefit: "Reducir carga cognitiva para consumidores (humanos y agentes)"
- name: "Secure by Design"
description: "Auth, rate limiting, input validation en todo endpoint"
reference: "Doc 08 - Auth, Doc 11 - Compliance"
- name: "AI-Consumable"
description: "Endpoints que un agente IA pueda descubrir y usar via MCP"
reference: "Doc 03 - MCP Servers"
- name: "Observable"
description: "Toda llamada genera traces, metricas y logs"
reference: "Doc 09 - Observabilidad"

https://api.{domain}.{tld}/v{major}/{resource}
Ejemplos:
https://api.myapp.co/v1/customers
https://api.myapp.co/v1/orders/{order_id}/items
https://api.myapp.co/v1/ai/chat # Endpoints IA
https://api.myapp.co/v1/ai/analyze # Endpoints IA
naming_conventions:
resources:
- "Sustantivos en plural: /customers, /orders, /products"
- "Lowercase con hyphens: /tax-rules, /payment-methods"
- "Maximo 3 niveles de anidamiento: /orders/{id}/items/{item_id}"
- "Evitar verbos en URLs (usar HTTP methods)"
query_parameters:
- "snake_case: ?page_size=20&sort_by=created_at"
- "Filtros con prefijo: ?filter_status=active&filter_country=CO"
- "Busqueda: ?search=texto libre"
- "Campos especificos: ?fields=id,name,email"
request_body:
- "camelCase en JSON (JavaScript convention)"
- "Alternativa: snake_case si el backend es Python-first"
- "IMPORTANTE: ser consistente en TODO el API"
special_endpoints:
- "POST /v1/ai/chat → Conversaciones con agentes"
- "POST /v1/ai/analyze → Analisis AI (DOFA, clasificacion, etc.)"
- "GET /v1/ai/suggestions → Sugerencias proactivas"
- "POST /v1/search → Busqueda semantica (RAG)"

{
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"type": "customer",
"attributes": {
"name": "Empresa ABC",
"email": "contacto@empresa.co",
"country": "CO",
"createdAt": "2026-03-10T14:30:00Z"
}
},
"meta": {
"requestId": "req_abc123",
"timestamp": "2026-03-10T14:30:01Z",
"version": "v1"
}
}
{
"data": [
{ "id": "...", "type": "customer", "attributes": { "..." } },
{ "id": "...", "type": "customer", "attributes": { "..." } }
],
"meta": {
"requestId": "req_abc456",
"timestamp": "2026-03-10T14:30:01Z",
"version": "v1"
},
"pagination": {
"page": 1,
"pageSize": 20,
"totalItems": 143,
"totalPages": 8,
"hasNext": true,
"hasPrev": false
}
}
{
"data": {
"id": "analysis_789",
"type": "ai_analysis",
"attributes": {
"result": "...",
"confidence": 0.92,
"model": "claude-sonnet-4-5",
"sources": [
{
"chunkId": "chunk_abc",
"documentTitle": "Politica de credito v3",
"relevanceScore": 0.95
}
]
}
},
"meta": {
"requestId": "req_xyz",
"timestamp": "2026-03-10T14:30:02Z",
"version": "v1",
"ai": {
"modelUsed": "claude-sonnet-4-5",
"tokensUsed": 1250,
"latencyMs": 1800,
"cached": false,
"disclaimer": "Respuesta generada por IA. Verifique con fuentes oficiales."
}
}
}
{
"error": {
"code": "VALIDATION_ERROR",
"status": 422,
"message": "Los datos enviados no son validos",
"details": [
{
"field": "email",
"rule": "format",
"message": "Formato de email invalido"
},
{
"field": "country",
"rule": "enum",
"message": "Pais debe ser un codigo ISO 3166-1 alpha-2 valido"
}
],
"requestId": "req_err123",
"timestamp": "2026-03-10T14:30:03Z",
"documentation": "https://docs.myapp.co/errors/VALIDATION_ERROR"
}
}
error_codes:
# 4xx Client Errors
VALIDATION_ERROR: { status: 422, description: "Input validation failed" }
NOT_FOUND: { status: 404, description: "Resource not found" }
UNAUTHORIZED: { status: 401, description: "Missing or invalid auth token" }
FORBIDDEN: { status: 403, description: "Insufficient permissions" }
CONFLICT: { status: 409, description: "Resource state conflict (optimistic lock)" }
RATE_LIMITED: { status: 429, description: "Too many requests" }
TENANT_MISMATCH: { status: 403, description: "Cross-tenant access attempted" }
# 5xx Server Errors
INTERNAL_ERROR: { status: 500, description: "Unexpected server error" }
SERVICE_UNAVAILABLE: { status: 503, description: "Dependency unavailable" }
# AI-specific errors
AI_TIMEOUT: { status: 504, description: "LLM response timeout" }
AI_RATE_LIMITED: { status: 429, description: "LLM rate limit reached" }
AI_CONTENT_FILTERED: { status: 422, description: "Content filtered by guardrails" }
AI_LOW_CONFIDENCE: { status: 200, description: "Response returned but low confidence" }
AI_BUDGET_EXCEEDED: { status: 429, description: "Daily AI budget exceeded" }

4.1 Endpoint Template (ejemplo Python/FastAPI)

Sección titulada «4.1 Endpoint Template (ejemplo Python/FastAPI)»

Nota: Este ejemplo usa Python/FastAPI. Adaptar al stack elegido en project-config.yaml. Los patrones (paginacion, error handling, health check) son universales.

from fastapi import APIRouter, Depends, Query, HTTPException, Request
from uuid import UUID
from pydantic import BaseModel, Field
from datetime import datetime
router = APIRouter(prefix="/v1/customers", tags=["Customers"])
# --- Request/Response Models ---
class CustomerCreate(BaseModel):
name: str = Field(min_length=1, max_length=200)
email: str = Field(pattern=r"^[\w.-]+@[\w.-]+\.\w+$")
country: str = Field(min_length=2, max_length=2) # ISO 3166-1
tax_id: str | None = None
metadata: dict = {}
class CustomerResponse(BaseModel):
id: UUID
type: str = "customer"
attributes: dict
class PaginatedResponse(BaseModel):
data: list
meta: dict
pagination: dict
# --- Endpoints ---
@router.get("/", response_model=PaginatedResponse)
async def list_customers(
request: Request,
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
sort_by: str = Query("created_at"),
sort_dir: str = Query("desc", pattern="^(asc|desc)$"),
filter_status: str | None = Query(None),
filter_country: str | None = Query(None),
search: str | None = Query(None),
tenant_id: UUID = Depends(get_current_tenant), # From JWT
):
"""List customers with pagination, filtering and search."""
filters = {}
if filter_status:
filters["status"] = filter_status
if filter_country:
filters["country"] = filter_country
items, total = await customer_repo.list(
tenant_id=tenant_id,
filters=filters,
search=search,
page=page,
page_size=page_size,
order_by=sort_by,
order_dir=sort_dir
)
return PaginatedResponse(
data=[_format_customer(c) for c in items],
meta={
"requestId": request.state.request_id,
"timestamp": datetime.utcnow().isoformat(),
"version": "v1"
},
pagination={
"page": page,
"pageSize": page_size,
"totalItems": total,
"totalPages": (total + page_size - 1) // page_size,
"hasNext": page * page_size < total,
"hasPrev": page > 1
}
)
@router.post("/", status_code=201)
async def create_customer(
request: Request,
body: CustomerCreate,
tenant_id: UUID = Depends(get_current_tenant),
user_id: UUID = Depends(get_current_user),
):
"""Create a new customer."""
customer = await customer_repo.save(
Customer(
tenant_id=tenant_id,
created_by=user_id,
**body.model_dump()
)
)
return {
"data": _format_customer(customer),
"meta": {
"requestId": request.state.request_id,
"timestamp": datetime.utcnow().isoformat(),
"version": "v1"
}
}
@router.get("/{customer_id}")
async def get_customer(
customer_id: UUID,
tenant_id: UUID = Depends(get_current_tenant),
):
"""Get a single customer by ID."""
customer = await customer_repo.get_by_id(customer_id, tenant_id)
if not customer:
raise HTTPException(status_code=404, detail={
"code": "NOT_FOUND",
"message": f"Customer {customer_id} not found"
})
return {"data": _format_customer(customer)}
from fastapi import APIRouter
from fastapi.responses import StreamingResponse
ai_router = APIRouter(prefix="/v1/ai", tags=["AI"])
@ai_router.post("/chat")
async def chat(
request: Request,
body: ChatRequest,
tenant_id: UUID = Depends(get_current_tenant),
user_id: UUID = Depends(get_current_user),
):
"""
Chat endpoint con agente IA.
Soporta streaming (SSE) y non-streaming.
"""
# 1. Check budget (Ref Doc 18 - FinOps)
budget_ok = await cost_guard.check_budget(tenant_id)
if not budget_ok:
raise HTTPException(status_code=429, detail={
"code": "AI_BUDGET_EXCEEDED",
"message": "Presupuesto diario de IA agotado"
})
# 2. Input guard (Ref Doc 04 - Guardrails)
sanitized = await input_guard.check(body.message)
# 3. Check semantic cache (Ref Doc 18)
cached = await semantic_cache.get(
query=sanitized.text,
tenant_id=tenant_id
)
if cached:
return _format_ai_response(cached, from_cache=True)
# 4. Execute agent
if body.stream:
return StreamingResponse(
_stream_agent_response(sanitized, tenant_id, user_id),
media_type="text/event-stream"
)
result = await agent.run(
message=sanitized.text,
session_id=body.session_id,
tenant_id=tenant_id,
user_id=user_id
)
# 5. Cache response
await semantic_cache.set(
query=sanitized.text,
response=result,
tenant_id=tenant_id
)
# 6. Return with AI metadata
return _format_ai_response(result, from_cache=False)
@ai_router.post("/analyze")
async def analyze(
request: Request,
body: AnalysisRequest,
tenant_id: UUID = Depends(get_current_tenant),
):
"""
Endpoint generico de analisis IA.
Tipos: dofa, classification, summary, extraction, etc.
"""
analysis_type = body.analysis_type # dofa, classify, summarize, extract
result = await analysis_agent.run(
analysis_type=analysis_type,
data=body.data,
parameters=body.parameters,
tenant_id=tenant_id
)
return {
"data": {
"id": result.id,
"type": "ai_analysis",
"attributes": {
"analysisType": analysis_type,
"result": result.output,
"confidence": result.confidence,
"sources": result.sources
}
},
"meta": {
"requestId": request.state.request_id,
"ai": {
"modelUsed": result.model,
"tokensUsed": result.tokens,
"latencyMs": result.latency_ms,
"disclaimer": "Analisis generado por IA. Requiere validacion humana."
}
}
}

versioning:
strategy: "url_path" # /v1/resource, /v2/resource
rules:
major_version: # v1 → v2
trigger: "Breaking changes (remove field, rename endpoint, change response format)"
policy: "Mantener version anterior activa minimo 6 meses con deprecation headers"
minor_version: # No cambia URL
trigger: "New fields (additive), new endpoints"
policy: "Backward compatible, no requiere nueva version"
deprecation:
header: "Deprecation: true"
sunset_header: "Sunset: Sat, 01 Sep 2026 00:00:00 GMT"
notice_period: "6 meses minimo"
documentation: "Migration guide obligatoria"
# Headers informativos en toda respuesta
response_headers:
X-API-Version: "v1"
X-Request-Id: "${REQUEST_ID}"
X-RateLimit-Limit: "1000"
X-RateLimit-Remaining: "995"
X-RateLimit-Reset: "1710086400"
breaking_changes: # Requieren nueva version mayor
- "Eliminar un campo del response"
- "Cambiar el tipo de un campo (string → number)"
- "Renombrar un endpoint"
- "Cambiar comportamiento de un endpoint existente"
- "Hacer obligatorio un campo que era opcional"
- "Cambiar formato de autenticacion"
non_breaking_changes: # No requieren nueva version
- "Agregar un campo nuevo al response"
- "Agregar un endpoint nuevo"
- "Agregar un query parameter opcional"
- "Agregar un nuevo error code"
- "Mejorar mensajes de error"
- "Optimizar performance"

from dataclasses import dataclass
@dataclass
class RateLimitConfig:
"""Configuracion de rate limiting por plan."""
plans = {
"free": {
"requests_per_minute": 30,
"requests_per_day": 1_000,
"ai_requests_per_day": 50,
"max_upload_mb": 5,
"concurrent_requests": 5
},
"starter": {
"requests_per_minute": 100,
"requests_per_day": 10_000,
"ai_requests_per_day": 500,
"max_upload_mb": 25,
"concurrent_requests": 10
},
"pro": {
"requests_per_minute": 500,
"requests_per_day": 100_000,
"ai_requests_per_day": 5_000,
"max_upload_mb": 100,
"concurrent_requests": 50
},
"enterprise": {
"requests_per_minute": 2_000,
"requests_per_day": 1_000_000,
"ai_requests_per_day": 50_000,
"max_upload_mb": 500,
"concurrent_requests": 200
}
}

api_security:
authentication:
- "JWT Bearer token en Authorization header"
- "Token expiry: 15 min access, 7 days refresh (Ref Doc 08)"
- "API Keys para integraciones server-to-server"
- "CORS configurado (solo origenes permitidos)"
authorization:
- "RBAC (Role-Based Access Control)"
- "Tenant isolation en TODA query (Ref Doc 08 - RLS)"
- "Permisos granulares por endpoint y operacion"
- "Agentes IA con permisos minimos (Ref Doc 03)"
input_validation:
- "Pydantic/Zod schemas para todo request body"
- "Query parameters tipados y con limites"
- "File uploads: tipo MIME, tamano, antivirus"
- "SQL injection protection (parameterized queries)"
- "Prompt injection detection (Ref Doc 04 - Guardrails)"
output:
- "No exponer stack traces en produccion"
- "No exponer IDs internos de infraestructura"
- "PII: solo si es necesario y con consentimiento"
- "AI responses: incluir disclaimer obligatorio"
infrastructure:
- "HTTPS obligatorio (TLS 1.3)"
- "Rate limiting por tenant y por plan"
- "Request size limit (default 10MB)"
- "Timeout global (30s default, 120s para AI endpoints)"
- "Health check endpoint: GET /health"

# openapi.yaml - Template base para todo proyecto
openapi: "3.1.0"
info:
title: "${PROJECT_NAME} API"
version: "1.0.0"
description: "API for ${PROJECT_DESCRIPTION}"
contact:
email: "${CONTACT_EMAIL}"
servers:
- url: "https://api.${DOMAIN}/v1"
description: "Production"
- url: "https://api.staging.${DOMAIN}/v1"
description: "Staging"
- url: "http://localhost:8000/v1"
description: "Development"
security:
- bearerAuth: []
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
schemas:
# Schemas reutilizables
PaginationMeta:
type: object
properties:
page: { type: integer }
pageSize: { type: integer }
totalItems: { type: integer }
totalPages: { type: integer }
hasNext: { type: boolean }
hasPrev: { type: boolean }
Error:
type: object
properties:
error:
type: object
properties:
code: { type: string }
status: { type: integer }
message: { type: string }
details: { type: array, items: { type: object } }
requestId: { type: string }
timestamp: { type: string, format: date-time }
AIMeta:
type: object
properties:
modelUsed: { type: string }
tokensUsed: { type: integer }
latencyMs: { type: integer }
cached: { type: boolean }
disclaimer: { type: string }
responses:
NotFound:
description: "Resource not found"
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
Unauthorized:
description: "Missing or invalid authentication"
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
RateLimited:
description: "Rate limit exceeded"
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
# Paths se definen por modulo/bounded context
paths:
/health:
get:
summary: "Health check"
security: []
responses:
"200":
description: "Service is healthy"
content:
application/json:
schema:
type: object
properties:
status: { type: string, example: "ok" }
version: { type: string }
timestamp: { type: string, format: date-time }

# Patron para APIs que seran consumidas por agentes via MCP (Ref Doc 03)
mcp_api_pattern:
discovery:
- "Endpoint de descubrimiento: GET /v1/api-schema"
- "Descripcion clara en cada endpoint (OpenAPI description)"
- "Ejemplos en cada schema"
- "Nombres de campos descriptivos (no abreviaciones)"
agent_friendliness:
- "Respuestas JSON consistentes y predecibles"
- "Errores informativos (el agente necesita entender que salio mal)"
- "Campos de tipo/categoria para ayudar al agente a filtrar"
- "Limites claros en documentacion (max records, allowed values)"
safety:
- "Read-only endpoints separados de write endpoints"
- "Write endpoints con confirmation flag para HITL"
- "Rate limiting especifico para agentes (mas restrictivo)"
- "Audit log de toda accion ejecutada por agente"

api_design_checklist:
contract:
- "[ ] OpenAPI spec escrito ANTES del codigo"
- "[ ] Todos los endpoints documentados con descripcion y ejemplos"
- "[ ] Request/Response schemas definidos con validacion"
- "[ ] Error codes estandarizados"
- "[ ] Versionado definido (URL path)"
implementation:
- "[ ] Paginacion en todos los list endpoints"
- "[ ] Filtrado y busqueda soportados"
- "[ ] Rate limiting configurado por plan"
- "[ ] Auth + tenant isolation en todo endpoint"
- "[ ] Input validation en todo request"
- "[ ] Health check endpoint: GET /health"
ai_endpoints:
- "[ ] /ai/chat con soporte streaming (SSE)"
- "[ ] /ai/analyze para analisis genericos"
- "[ ] Semantic cache integrado"
- "[ ] Budget guard por tenant"
- "[ ] Disclaimer en toda respuesta IA"
- "[ ] Sources/citations incluidas"
quality:
- "[ ] Tests de contrato (schema validation)"
- "[ ] Tests de integracion por endpoint"
- "[ ] Performance: p95 < 200ms (non-AI), < 3s (AI)"
- "[ ] Security: OWASP API Top 10 revisado"
- "[ ] Documentation publicada y actualizada"
operations:
- "[ ] Observability: traces por request (Ref Doc 09)"
- "[ ] Monitoring: latency, error rate, throughput"
- "[ ] Alertas: error rate > 5%, p95 > threshold"
- "[ ] Logs estructurados con request_id y tenant_id"

TemaDocumento
Domain Model (schemas)Doc 19 --- Data Architecture & Domain Modeling
Auth & Tenant IsolationDoc 08 --- Backup, DR, Database & Auth
MCP Server IntegrationDoc 03 --- MCP Servers & Conectores
Guardrails for AI endpointsDoc 04 --- Patrones Agenticos
Observability per requestDoc 09 --- Observabilidad, FinOps & Alertas
Semantic CacheDoc 18 --- Semantic Cache & Performance AI
Compliance (PII, GDPR)Doc 11 --- Compliance, Regulacion & ISO
UX consuming APIsDoc 15 --- UX/AI Experience Design
Multi-Country formattingDoc 21 --- Multi-Country & Internationalization

AI-First Engineering Framework --- Baseline v6.5