Contratos de API y versionado | Nicolás Garzón
Una API es un contrato de comportamiento, no una lista de endpoints. Incluye semántica, identidad, errores, idempotencia, consistencia, límites, compatibilidad y ciclo de vida.
Una API permite que un consumidor use una capacidad sin conocer internals.
Texto
Copiar consumer
→ contrato estable
→ providerLa estabilidad no significa ausencia de cambios. Significa evolucionar sin romper consumidores inesperadamente.
Qué puede solicitarse y con qué intención.
Campos, tipos, unidades, timezone, nulabilidad y significado.
Estados de éxito, rechazo de negocio y fallo técnico.
Timeouts, asincronía, consistencia y estados pendientes.
Autenticación, autorización, tenant y alcance.
Paginación, rate limits, tamaño y cuotas.
Compatibilidad, versión, deprecación y soporte.
Un endpoint debería expresar una capacidad, no exponer tablas.
Texto
Copiar POST /orders/{id}/confirmpuede ser más preciso que:
Texto
Copiar PATCH /orders/{id} { status: "confirmed" }El primero permite validar transición, identidad e idempotencia. El segundo parece permitir cambiar estado arbitrariamente.
Una respuesta útil distingue categorías:
JSON
Copiar {
"type" : "https://nicoo.dev/problems/insufficient-stock" ,
"title" : "Insufficient stock" ,
"status" : 409 ,
"detail" : "Product p-42 has only 2 units available" ,
"instance" : "/orders/o-10/confirm" ,
"requestId" : "req-99"
} No expongas stack traces ni errores del ORM. Tampoco conviertas todo en 500.
Crear pagos, pedidos o reservas puede repetirse por timeout.
Texto
Copiar Idempotency-Key
+ actor
+ operación
+ hash del payload
→ resultado persistidoUna repetición con la misma intención devuelve el resultado previo. Reutilizar la clave con payload distinto debe rechazarse.
Simple, pero cambios concurrentes pueden producir duplicados o saltos; grandes offsets son costosos.
Usa una posición estable:
Texto
Copiar createdAt + idNecesita orden determinista y cursor opaco. Define comportamiento si el recurso cambia entre páginas.
Una respuesta debe aclarar qué representa.
Texto
Copiar 202 Accepted
→ operación registrada
→ resultado todavía pendienteNo devuelvas éxito final cuando solo encolaste trabajo.
Para read-your-writes, puedes devolver el nuevo estado directamente o permitir consultar una operación.
Cambios aditivos, campos opcionales y nuevos endpoints pueden mantenerse en la misma versión si consumidores son tolerantes.
Ruta, header o media type. Cada estrategia tiene tooling y visibilidad distintos.
Versionar no evita migrar. Mantener versiones permanentes aumenta superficie y pruebas.
Eliminar o renombrar campos.
Cambiar unidad o significado.
Hacer requerido un campo opcional.
Cambiar null por ausencia.
Añadir enum si consumidores son exhaustivos.
Modificar orden o idempotencia.
Cambiar autenticación.
Un schema aditivo puede ser semánticamente incompatible.
Añade nueva forma compatible.
Proveedor soporta ambas.
Consumidores migran.
Telemetría confirma abandono.
Se depreca y elimina la antigua.
Para datos, puede requerir doble lectura, backfill o versiones.
Los consumidores publican expectativas verificables. El provider ejecuta esos contratos antes de desplegar.
Aportan detección temprana, pero no prueban:
Rendimiento.
Seguridad completa.
Compatibilidad con consumidores desconocidos.
Semántica no expresada.
También requieren disciplina si cruzan equipos o despliegues. Un API “privada” puede tener más consumidores de los conocidos.
Mantén catálogo, owner, documentación y telemetría de uso.
JavaScript
Copiar POST / orders/ o- 42 / confirm
Idempotency- Key: confirm- o- 42 - v1
Authorization : Bearer ...
200 confirmed: ya estaba confirmado por la misma intención.
409 invalid-state: no puede confirmarse.
409 insufficient-stock: rechazo de dominio.
202 pending: resultado externo desconocido.
401/403: identidad o autorización.
429: cuota.
El contrato debe documentar qué respuestas pueden reintentarse.
Timeout recomendado.
Operaciones idempotentes.
Retry-After para límites.
Request ID para soporte.
Consulta de estado si el resultado es desconocido.
El provider no puede garantizar que un timeout signifique que no ejecutó la operación.
Son APIs asíncronas y requieren:
Firma y timestamp.
Event ID.
Retries.
Idempotencia.
Orden no garantizado.
Endpoint de consulta/reconciliación.
Rotación de secretos.
Responder rápido y procesar de forma segura.
Autorización por recurso.
Rate limiting por identidad/tenant.
Validación de tamaño y contenido.
Redacción de logs.
Protección contra enumeración.
Scope mínimo.
El ID enviado por cliente no demuestra acceso.
Mide por endpoint y consumidor:
Latencia y errores.
Versiones usadas.
Rate limits.
Deprecation warnings.
Idempotency conflicts.
Operaciones pendientes.
Sin telemetría no sabes cuándo retirar una versión.
Define periodo realista y contacto. No elimines basado solo en ausencia reciente si el uso es estacional.
La ausencia debe tener semántica explícita; no uses defaults ambiguos.
Consumers deben manejar fallback o error controlado.
Tenant se deriva de identidad; no se acepta ciegamente del payload.
Define atomicidad parcial, errores por item, límite e idempotencia.
API como espejo de DB.
200 para operación pendiente.
Errores genéricos.
Versionar todo o nunca versionar.
Romper enums aditivamente.
Sin política de deprecación.
Confiar en contract tests de forma únicamente.
No documentar retries.
Schema validation.
Contract tests.
Integration tests.
Pruebas de autorización.
Idempotencia y concurrencia.
Compatibilidad entre versiones.
Load tests y rate limits.
Webhook duplicates/out-of-order.
Una API incluye semántica y temporalidad.
Los cambios aditivos no siempre son compatibles.
Idempotencia protege retries de creación.
Versionado necesita migración y telemetría.
APIs internas también son contratos.
Los errores deben guiar la acción del consumidor.
¿Por qué PATCH status puede debilitar invariantes?
¿Qué debe incluir una idempotency key?
¿Cuándo usarías 202 Accepted?
¿Por qué agregar un enum puede romper?
¿Qué no prueba un consumer contract?
Ownership de datos y arquitectura de persistencia conecta contratos con fuentes de verdad, modelos y migraciones.