Definición
Un contrato de API es una especificación formal y legible por máquina que define con precisión cómo funciona una API. Describe cada endpoint, qué entradas acepta cada uno (esquemas de petición), qué salidas devuelve (esquemas de respuesta), requisitos de autenticación, códigos de error, límites de velocidad y garantías de comportamiento. Piensa en él como un contrato legal entre el proveedor de la API y el consumidor - especifica exactamente qué pueden esperar ambas partes de la interacción.
A diferencia de la documentación informal que podría describir una API en prosa, un contrato es inequívoco y ejecutable. Las herramientas pueden validar que las implementaciones realmente coinciden con el contrato, que el código cliente usa correctamente la API, y que los cambios en cualquier lado mantienen la compatibilidad hacia atrás. Los contratos habilitan el desarrollo paralelo - los equipos de backend implementan el contrato mientras los equipos de frontend codifican contra él simultáneamente, usando servidores mock que siguen el contrato.
En la práctica, los contratos de API típicamente se escriben como especificaciones OpenAPI, esquemas GraphQL, Protocol Buffers o formatos similares. El contrato se convierte en la fuente única de verdad - la documentación se genera desde él, los SDKs se generan desde él, las pruebas validan contra él, y los API gateways lo hacen cumplir.
Ejemplo
Comunicación entre Microservicios: Un servicio de pagos y un servicio de pedidos necesitan comunicarse. Antes de escribir código, los equipos definen un contrato: POST /payments acepta orderId (UUID), amount (número), currency (código de 3 letras), devuelve paymentId y status. Ambos equipos codifican según este contrato. Las pruebas de integración validan que las llamadas reales a la API coincidan con el contrato.
Integración con API de Terceros: Stripe publica su contrato de API como una especificación OpenAPI. Cuando integras pagos con Stripe, descargas su contrato, generas un SDK con tipos seguros desde él, y tu IDE autocompleta las llamadas a métodos con los parámetros correctos. Si Stripe cambia su contrato (cambio incompatible), tu generación de SDK falla, alertándote inmediatamente.
Desarrollo de Aplicación Móvil: Un equipo construyendo aplicaciones iOS y Android necesita una API backend. Backend define el contrato primero: GET /users/{id} devuelve un objeto User con campos específicos. Los equipos móviles inmediatamente comienzan a construir la UI usando el contrato para generar datos mock, sin esperar la implementación del backend.
Cumplimiento de SLA: Un contrato especifica que GET /products debe responder dentro de 200ms en el percentil 99, manejar 10,000 peticiones/segundo, y mantener 99.9% de uptime. Las herramientas de monitorización validan la API en vivo contra estos términos del contrato, activando alertas cuando ocurren violaciones del SLA.
Detección de Cambios Incompatibles: Un equipo quiere renombrar “userId” a “id” en las respuestas de la API. Las herramientas de prueba de contratos comparan el cambio propuesto contra el contrato existente y lo marcan como un cambio incompatible - los clientes existentes esperan userId. El equipo mantiene userId para compatibilidad hacia atrás o versiona la API apropiadamente.
Analogía
El Plano del Edificio: Antes de construir un edificio, los arquitectos crean planos detallados especificando las dimensiones de cada habitación, ubicación de puertas, enchufes eléctricos, conexiones de fontanería. Los contratistas de diferentes oficios (eléctrico, fontanería, carpintería) trabajan simultáneamente usando el plano como contrato. Los inspectores verifican que el trabajo coincida con el plano. Un contrato de API es lo mismo - es el plano que múltiples equipos siguen para asegurar que todo conecta correctamente.
El Menú del Restaurante: Un menú es un contrato entre restaurante y cliente. Especifica qué platos están disponibles (endpoints), qué hay en cada plato (esquemas de petición/respuesta), precios (límites de velocidad), y tiempo de preparación (SLAs). Los clientes ordenan basándose en el contrato del menú. Si la cocina cambia una receta significativamente sin actualizar el menú, los clientes que ordenaron basándose en el contrato antiguo se sorprenderán.
El Contrato de Alquiler: Al alquilar un apartamento, firmas un contrato especificando monto de renta, fecha de vencimiento, responsabilidades de mantenimiento, condiciones de terminación. Tanto propietario como inquilino saben exactamente qué se espera. Si el propietario repentinamente cambia la renta a mitad del contrato sin enmienda, está en incumplimiento. Los contratos de API funcionan de la misma manera - los cambios requieren negociación (versionado).
Ejemplo de Código
# Contrato de API como Especificación OpenAPI
openapi: 3.1.0
info:
title: Order Management API
version: 2.1.0
description: |
Contrato para sistema de procesamiento de pedidos.
**Garantías SLA:**
- Tiempo de respuesta: 200ms (p99)
- Disponibilidad: 99.9%
- Límite de velocidad: 1000 peticiones/min por cliente
contact:
name: Soporte API
email: [email protected]
servers:
- url: https://api.example.com/v2
description: Servidor de producción
paths:
/orders:
post:
summary: Crear un nuevo pedido
description: |
Crea un pedido y devuelve inmediatamente con estado "pending".
El procesamiento del pedido ocurre de forma asíncrona.
operationId: createOrder
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateOrderRequest'
responses:
'201':
description: Pedido creado exitosamente
headers:
X-Request-ID:
schema:
type: string
format: uuid
description: Identificador único de petición para rastreo
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
'400':
description: Datos de petición inválidos
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
description: Límite de velocidad excedido
headers:
X-RateLimit-Limit:
schema:
type: integer
description: Peticiones permitidas por minuto
X-RateLimit-Remaining:
schema:
type: integer
description: Peticiones restantes en ventana actual
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/orders/{orderId}:
get:
summary: Obtener pedido por ID
operationId: getOrder
parameters:
- name: orderId
in: path
required: true
schema:
type: string
format: uuid
responses:
'200':
description: Pedido encontrado
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
'404':
description: Pedido no encontrado
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
schemas:
CreateOrderRequest:
type: object
required:
- customerId
- items
properties:
customerId:
type: string
format: uuid
description: UUID del cliente que hace el pedido
items:
type: array
minItems: 1
maxItems: 100
items:
type: object
required: [productId, quantity]
properties:
productId:
type: string
format: uuid
quantity:
type: integer
minimum: 1
maximum: 1000
shippingAddress:
$ref: '#/components/schemas/Address'
Order:
type: object
required:
- id
- customerId
- items
- status
- total
- createdAt
properties:
id:
type: string
format: uuid
customerId:
type: string
format: uuid
items:
type: array
items:
type: object
required: [productId, quantity, price]
properties:
productId:
type: string
format: uuid
quantity:
type: integer
price:
type: number
description: Precio por unidad en USD
status:
type: string
enum:
- pending
- processing
- shipped
- delivered
- cancelled
description: Estado actual del pedido
total:
type: number
description: Monto total del pedido en USD
createdAt:
type: string
format: date-time
shippingAddress:
$ref: '#/components/schemas/Address'
Address:
type: object
required:
- street
- city
- postalCode
- country
properties:
street:
type: string
maxLength: 200
city:
type: string
maxLength: 100
postalCode:
type: string
pattern: "^[0-9]{5}(-[0-9]{4})?$"
country:
type: string
pattern: "^[A-Z]{2}$"
description: Código de país ISO 3166-1 alpha-2
Error:
type: object
required:
- code
- message
properties:
code:
type: string
description: Código de error legible por máquina
message:
type: string
description: Mensaje de error legible por humano
details:
type: array
items:
type: object
properties:
field:
type: string
issue:
type: string
securitySchemes:
BearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
security:
- BearerAuth: []
Diagrama
graph TB
subgraph "Contrato de API"
CONTRACT[Especificación OpenAPI]
CONTRACT --> ENDPOINTS[Endpoints
Rutas y Métodos]
CONTRACT --> REQ[Esquemas de Petición
Validación de Entrada]
CONTRACT --> RESP[Esquemas de Respuesta
Formato de Salida]
CONTRACT --> AUTH[Autenticación
Esquemas de Seguridad]
CONTRACT --> SLA[Términos SLA
Rendimiento, Límites]
end
subgraph "Lado Proveedor"
IMPL[Implementación]
TESTS[Pruebas de Contrato]
IMPL -.Valida Contra.-> CONTRACT
TESTS -.Hace Cumplir.-> CONTRACT
end
subgraph "Lado Consumidor"
SDK[SDK Generado]
MOCK[Servidor Mock]
CLIENT[Código Cliente]
CONTRACT -.Genera.-> SDK
CONTRACT -.Alimenta.-> MOCK
SDK --> CLIENT
end
CONTRACT --> DOCS[Documentación
Auto-Generada]
style CONTRACT fill:#90EE90
style IMPL fill:#87CEEB
style SDK fill:#FFD700
Buenas Prácticas
- Desarrollo contract-first - Define el contrato antes de escribir código de implementación
- Versiona explícitamente - Usa versionado semántico en el contrato (1.0.0, 2.0.0)
- Prueba contra el contrato - Ejecuta pruebas de contrato asegurando que la implementación coincida con la especificación
- Genera SDKs desde el contrato - No codifiques clientes a mano, genéralos para mantenerse sincronizados
- Documenta SLAs en el contrato - Incluye garantías de rendimiento, límites de velocidad como metadata
- Usa el contrato para mocking - Genera servidores mock desde el contrato para desarrollo paralelo
- Detecta cambios incompatibles - Usa herramientas para comparar versiones del contrato y marcar cambios incompatibles
- Haz los contratos descubribles - Publica contratos en un registro central o catálogo de APIs
Errores Comunes
Sin contrato alguno: Construir APIs solo con documentación informal o archivos README. Esto lleva a discrepancias entre documentación e implementación.
Implementation-first: Escribir código primero, luego extraer un contrato desde él. Esto pierde el beneficio principal - los contratos habilitan desarrollo paralelo y discusiones de diseño antes de codificar.
Contratos obsoletos: Escribir un contrato una vez y luego dejarlo divergir a medida que la API evoluciona. Contrato e implementación deben mantenerse sincronizados.
Contratos incompletos: Solo documentar casos felices (respuestas 200) e ignorar casos de error, casos límite, límites de velocidad, detalles de autenticación.
No probar contra el contrato: Tratar el contrato solo como documentación, no como pruebas ejecutables. Las pruebas de contrato deberían ejecutarse en CI/CD.
Cambios incompatibles sin versionado: Modificar el contrato de formas incompatibles hacia atrás sin incrementar la versión mayor, rompiendo clientes existentes.
Contratos demasiado rígidos: Hacer contratos tan específicos que son difíciles de evolucionar. Balancea precisión con flexibilidad para adiciones no incompatibles.