API-first: como disenar servicios pensados para integrarse
La API es el producto
Hay un momento en la vida de toda empresa tecnologica en el que alguien dice “necesitamos una API”. Generalmente ocurre cuando un cliente pide integrarse, un partner necesita datos, o el equipo de frontend esta harto de que el backend cambie sin aviso. Lo que deberia haber ocurrido es que la API existiera desde el primer dia.
API-first no es una moda. Es una decision de arquitectura que dice: el contrato de la API se disena antes que la implementacion. No al reves. No como un subproducto del codigo. No como una capa que se pone encima al final. El contrato primero, la implementacion despues.
Por que importa? Porque cuando la API es un afterthought, refleja las peculiaridades internas del sistema. Nombres de campos que solo tienen sentido si conoces el modelo de datos. Errores que devuelven stack traces en produccion. Paginacion inconsistente entre endpoints. Versiones que rompen sin aviso. Hemos visto todo esto en integraciones reales con ERPs y sistemas de terceros, y el coste en horas de debugging y frustacion es enorme.
Contract-first con OpenAPI
OpenAPI (antes Swagger) es el estandar de facto para describir APIs REST. Un fichero OpenAPI es un contrato formal que define endpoints, parametros, schemas de request/response, codigos de error, y autenticacion. Escribirlo antes de implementar tiene beneficios concretos:
Paralelizacion del trabajo. Con el contrato definido, frontend y backend pueden trabajar en paralelo. Frontend genera mocks desde el spec. Backend implementa contra el contrato. QA escribe tests contra el contrato. Nadie espera a nadie.
Generacion de codigo. Herramientas como openapi-generator producen SDKs de cliente en 40+ lenguajes, stubs de servidor, y tipos TypeScript directamente desde el spec. No es codigo perfecto, pero elimina la escritura manual de DTOs, validaciones y clientes HTTP.
Validacion automatica. Middleware como express-openapi-validator (Node.js) o connexion (Python) valida requests y responses contra el spec en runtime. Si un endpoint devuelve un campo que no esta en el contrato, falla. Esto atrapa inconsistencias entre spec y codigo antes de que lleguen a produccion.
Estructura de un buen spec OpenAPI
openapi: 3.1.0
info:
title: Shipments API
version: 2.0.0
description: API for managing shipment lifecycle
servers:
- url: https://api.example.com/v2
description: Production
- url: https://staging-api.example.com/v2
description: Staging
paths:
/shipments:
get:
operationId: listShipments
summary: List shipments with pagination and filters
parameters:
- $ref: '#/components/parameters/PageCursor'
- $ref: '#/components/parameters/PageSize'
- name: status
in: query
schema:
$ref: '#/components/schemas/ShipmentStatus'
responses:
'200':
description: Paginated list of shipments
content:
application/json:
schema:
$ref: '#/components/schemas/ShipmentList'
'401':
$ref: '#/components/responses/Unauthorized'
Tres cosas que importan aqui:
operationIden todos los endpoints. Es lo que usa el generador de codigo para nombrar metodos. Sin el, los nombres generados son ilegibles.- Componentes reutilizables (
$ref). Paginacion, errores comunes, schemas compartidos. Definir una vez, usar en todas partes. Evita la inconsistencia que aparece cuando copias y pegas schemas. - Servidores explicitos. El spec debe funcionar en Swagger UI sin modificaciones. Un desarrollador externo deberia poder apuntar a staging y probar.
Versionado: la decision que pagaras durante anos
El versionado de APIs es una de esas decisiones que parecen menores al principio y se convierten en dolores de cabeza monumentales despues. Hay tres estrategias principales:
Versionado en URL (/v1/shipments, /v2/shipments): La mas explicita y la que recomendamos para APIs publicas. El consumidor sabe exactamente que version usa. El routing es trivial. La desventaja es que fuerza a mantener multiples versiones desplegadas.
Versionado en header (Accept: application/vnd.api+json;version=2): Mas limpia conceptualmente, pero mas dificil de descubrir y de probar con herramientas sencillas. Funciona bien para APIs internas con consumidores controlados.
Sin versionado explicito (evolucion compatible): Solo haces cambios compatibles hacia atras. Nunca rompes. Funciona si tienes disciplina de hierro y pocos consumidores. Se vuelve impracticable cuando necesitas cambios estructurales.
Nuestra recomendacion para la mayoria de los equipos: versionado en URL con una politica de deprecacion clara.
Politica de deprecacion
Una politica de deprecacion es un contrato social con tus consumidores. La nuestra es sencilla:
- Minimo 6 meses entre el anuncio de deprecacion y la eliminacion de una version.
- Anuncio via header
Sunset(RFC 8594) en todas las responses de la version deprecada. - Anuncio via email a los consumidores registrados.
- Dashboard con metricas de uso por version para saber cuando es seguro eliminar.
Sin una politica de deprecacion, acabas manteniendo versiones fantasma que nadie usa (o que un consumidor critico usa sin que lo sepas).
Errores: el diseno olvidado
La gestion de errores es donde mas APIs fallan. No tecnicamente (devuelven un 500 y se acabo), sino en la utilidad de la informacion que proporcionan al consumidor.
Estructura de errores estandar
Adoptar un formato de errores estandar como RFC 7807 (Problem Details for HTTP APIs) ahorra dolores de cabeza:
{
"type": "https://api.example.com/errors/validation-failed",
"title": "Validation Failed",
"status": 422,
"detail": "The shipment weight exceeds the maximum for the selected carrier.",
"instance": "/shipments/abc-123",
"errors": [
{
"field": "weight",
"message": "Must be less than 30000 grams for carrier DHL Express",
"value": 35000
}
]
}
Puntos clave:
typees una URI que identifica univocamente el tipo de error. Permite al consumidor programar logica especifica por tipo de error sin parsear mensajes de texto.detailes legible por humanos. Lo que el desarrollador vera en su consola cuando algo falle.errors(extension) proporciona detalle a nivel de campo para errores de validacion. El frontend puede mostrar el error junto al campo correcto sin adivinar.
Codigos HTTP: usarlos correctamente
Suena basico, pero el 30-40% de las APIs que integramos usan los codigos HTTP mal:
| Codigo | Significado real | Error comun |
|---|---|---|
| 400 | Request malformada (JSON invalido, campo faltante) | Usar para logica de negocio |
| 401 | No autenticado | Confundir con 403 |
| 403 | Autenticado pero sin permiso | Confundir con 401 |
| 404 | Recurso no existe | Devolver para errores de logica |
| 409 | Conflicto (recurso ya existe, estado invalido) | No usarlo |
| 422 | Validacion semantica fallida (los datos son JSON valido pero violan reglas de negocio) | Usar 400 para todo |
| 429 | Rate limit excedido | No implementar rate limiting |
| 500 | Error interno del servidor | Devolver con stack traces |
Si solo te llevas una cosa de esta seccion: 400 es “no puedo parsear tu request” y 422 es “puedo parsearla pero no tiene sentido para el negocio”. La distincion importa porque el consumidor puede reintentar un 500 pero no un 422.
Paginacion
Tres patrones, un ganador claro:
Offset-based (?page=3&per_page=20): Sencillo, intuitivo, y roto. Si se insertan o eliminan registros mientras el consumidor pagina, se saltan o duplican resultados. Para datos que cambian frecuentemente (pedidos, eventos), es inaceptable.
Cursor-based (?cursor=eyJpZCI6MTIzfQ&limit=20): Consistente independientemente de inserciones/eliminaciones. El cursor es un token opaco que apunta a la posicion en el dataset. Cada pagina incluye el cursor para la siguiente. Es el patron que usan Stripe, Shopify, y cualquier API seria.
Keyset-based (?after_id=123&limit=20): Similar a cursor pero con clave visible. Util cuando el consumidor quiere empezar desde un punto conocido.
Nuestra recomendacion: cursor-based para APIs publicas, con una response envelope estandar:
{
"data": [...],
"pagination": {
"next_cursor": "eyJpZCI6MTIzfQ",
"has_more": true
}
}
Autenticacion y rate limiting
API keys para server-to-server. Simple, funcional. La key va en el header Authorization: Bearer sk_live_.... Nunca en la URL (se loguea en proxies y CDNs). Prefijar las keys con el entorno (sk_live_, sk_test_) evita el error clasico de usar produccion en desarrollo.
OAuth 2.0 para accesos delegados. Cuando un usuario autoriza a una app a acceder en su nombre. Mas complejo, pero necesario para integraciones donde el usuario final importa.
Rate limiting explicitado en headers. Siempre devolver X-RateLimit-Limit, X-RateLimit-Remaining, y X-RateLimit-Reset. El consumidor puede adaptar su comportamiento sin trial-and-error. Cuando se excede el limite, devolver 429 con un header Retry-After.
Documentacion como codigo
La documentacion de API que vive fuera del codigo se desactualiza. Siempre. Sin excepcion. La unica documentacion fiable es la que se genera desde el spec OpenAPI o se valida contra el.
Herramientas que funcionan
- Redocly: Genera documentacion estatica desde OpenAPI. Limpia, personalizable, y se integra en CI/CD.
- Scalar: Alternativa moderna a Swagger UI. API reference interactiva con ejemplos ejecutables.
- Mintlify/Fern: Documentacion completa (guides + API reference) generada desde OpenAPI + markdown. Ideal para developer portals publicos.
Lo minimo viable
Cada endpoint necesita:
- Descripcion de que hace (no como se implementa).
- Ejemplo de request completo con datos realistas (no
"string"o"example"). - Ejemplo de response para el caso exitoso y para cada tipo de error.
- Codigo de ejemplo en al menos un lenguaje (curl + el lenguaje mas comun entre tus consumidores).
Lo que diferencia una buena documentacion de una mediocre no es la cantidad. Es la calidad de los ejemplos. Un ejemplo con datos realistas ("tracking_number": "1Z999AA10123456784") vale mas que diez paginas de descripcion abstracta.
Testing de contratos
El contrato OpenAPI no sirve de nada si no se valida. Hay dos niveles:
Validacion en CI: Herramientas como Spectral lintean el spec contra reglas de estilo. Es el equivalente a ESLint para APIs: nombres en camelCase, descriptions obligatorias, schemas reutilizados. Configurar Spectral con un ruleset corporativo estandariza el diseno entre equipos.
Contract testing: Herramientas como Prism (de Stoplight) o Schemathesis generan requests aleatorias a partir del spec y verifican que el servidor responde conforme al contrato. Pact es la alternativa cuando necesitas contract testing entre servicios (consumer-driven contracts).
Integrar ambos en el pipeline de CI significa que ningun cambio que rompa el contrato llega a produccion. Eso es lo que separa una API profesionalmente diseñada de una que “funciona en mi maquina”.
API-first en la practica: flujo de trabajo
- Diseno del spec en un fichero OpenAPI. Review en pull request como si fuera codigo. Los consumidores participan en la review.
- Generacion de mocks con Prism o Mockoon. Frontend y consumidores externos empiezan a integrar contra mocks.
- Implementacion del servidor contra el spec. Validacion automatica en runtime.
- Contract testing en CI. Spectral para linting, Schemathesis para fuzzing.
- Documentacion generada automaticamente. Publicada en cada merge a main.
- Monitoring de uso real. Metricas por endpoint (latencia, error rate, volumen) para detectar que se usa, que se rompe, y que deprecar.
El flujo parece mas trabajo al principio. Lo es. Pero cada hora invertida en disenar bien el contrato ahorra diez horas de debugging, reuniones de alineacion, y soporte a integradores. En un ecosistema con 5+ consumidores, la diferencia es brutal.
Reflexion final
API-first es una apuesta por el largo plazo. Cuesta mas al principio. Requiere disciplina. Obliga a pensar en los consumidores antes de escribir la primera linea de codigo. Pero las organizaciones que lo practican construyen sistemas que escalan no solo en trafico, sino en el numero de equipos y partners que pueden integrar sin que todo se desmonte. Y en un mundo donde la integracion es la norma, esa capacidad vale mas que cualquier feature individual.
Etiquetas
Sobre el autor
abemon engineering
Equipo de ingenieria
Equipo multidisciplinar de ingenieria, datos e IA con sede en Canarias. Construimos, desplegamos y operamos soluciones de software a medida para empresas de cualquier escala.

