Saltar al contenido principal
Volver al blog
API Arquitectura Escalabilidad

Diseñar APIs que escalen: 150+ proyectos reales

Patrones para diseñar APIs que escalen: versionado, paginación, rate limiting e idempotencia. Experiencia de 150+ proyectos.

JM
Javier Manzano
CEO & Co-founder • 28 de junio de 2026

Después de más de 150 proyectos con APIs en producción, hemos identificado patrones que se repiten una y otra vez. No son teorías académicas ni best practices de un tutorial. Son lecciones aprendidas en el campo, a veces a base de incidentes en producción, a veces por refactorizaciones dolorosas que podríamos haber evitado con mejores decisiones iniciales.

Este artículo recopila los principios de diseño de API que hemos destilado en Soamee tras años construyendo sistemas que procesan millones de peticiones diarias. Desde la API de certificación digital de eEvidence hasta la API de datos publicitarios de InfoAdex, pasando por las APIs de integración de Cawa y Xceed.

Principio 1: Diseña el contrato antes de escribir código

El error más común que vemos es empezar a escribir código sin un contrato de API definido. El resultado: endpoints inconsistentes, formatos de respuesta diferentes según quién programó cada parte, y una experiencia de integración caótica.

Nosotros seguimos el enfoque API-first:

  1. Workshop de discovery con stakeholders: qué datos necesita quién, con qué frecuencia, qué operaciones se requieren
  2. Especificación OpenAPI 3.1 completa: endpoints, request/response schemas, códigos de error, ejemplos
  3. Revisión con consumidores: el equipo frontend o los integradores validan el contrato antes del desarrollo
  4. Desarrollo contra la spec: el código se valida automáticamente contra la especificación en CI

Este enfoque reduce un 40% el tiempo de integración y elimina los “eso no es lo que yo esperaba” post-desarrollo.

Principio 2: La paginación correcta marca la diferencia

Hemos visto APIs en producción con endpoints que devuelven arrays de 50.000 elementos sin paginación. El servidor aguanta… hasta que no aguanta.

Paginación cursor-based vs offset-based

La paginación offset-based (?page=5&limit=20) es simple pero tiene problemas graves cuando los datos cambian entre peticiones: registros duplicados o perdidos si se insertan o eliminan items.

La paginación cursor-based (?after=cursor_abc&limit=20) resuelve estos problemas y es más eficiente en bases de datos grandes (no necesita contar filas para calcular el offset):

{
  "data": [...],
  "pagination": {
    "has_next": true,
    "next_cursor": "eyJpZCI6MTIzNH0=",
    "has_prev": true,
    "prev_cursor": "eyJpZCI6MTIwMH0="
  }
}

En InfoAdex, con más de 55 millones de registros, la paginación cursor-based fue la diferencia entre consultas de 2 segundos (offset con OFFSET grande) y consultas de 50ms (cursor que hace un WHERE id > last_seen_id).

Regla: siempre define un límite máximo

Incluso con paginación, siempre define un max_limit. Si el cliente pide ?limit=1000000, tu API debe responder con el máximo permitido (e.g., 100) y un header indicando que se truncó. Nunca confíes en que el consumidor será razonable.

Principio 3: Idempotencia o te arrepentirás

En sistemas distribuidos, las peticiones se pueden reintentar (timeouts de red, fallos de proxy, retries del cliente). Si tu endpoint POST /payments no es idempotente, un retry puede generar un cobro duplicado.

La solución: Idempotency Keys.

El cliente envía un header Idempotency-Key: unique-request-id. El servidor:

  1. Verifica si ya procesó esa key
  2. Si sí: devuelve la respuesta original almacenada
  3. Si no: procesa la petición y almacena la respuesta junto a la key

En Stripe se popularizó este patrón, y lo aplicamos en todos los endpoints que tienen side effects (crear, actualizar, eliminar). Las idempotency keys expiran tras 24-48 horas.

En el proyecto de eEvidence, la idempotencia fue crítica: un certificado digital no puede emitirse dos veces para la misma evidencia, incluso si el cliente reintentar por un timeout de red.

Principio 4: Rate limiting multi-dimensional

El rate limiting no es solo “100 requests por minuto”. En APIs reales necesitas múltiples dimensiones:

  • Por usuario/API key: Límite global del consumidor
  • Por endpoint: Endpoints costosos (reports, exports) con límites más bajos
  • Por tier: Free (100 req/min), Pro (1000 req/min), Enterprise (10000 req/min)
  • Por IP: Protección contra abuso incluso sin autenticación

Usamos el algoritmo token bucket con ventanas deslizantes implementado en Redis. Los headers de respuesta siempre incluyen:

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 847
X-RateLimit-Reset: 1719302400
Retry-After: 30

El Retry-After solo aparece cuando el límite se ha excedido (HTTP 429). Esto permite a los clientes implementar backoff automático sin adivinar cuánto esperar.

Principio 5: Errores que ayudan, no que frustran

Un error genérico {"error": "Bad request"} es inútil para un integrador. Nuestro formato de error estándar:

{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "The request body contains validation errors",
    "details": [
      {
        "field": "email",
        "code": "INVALID_FORMAT",
        "message": "Must be a valid email address",
        "value": "not-an-email"
      },
      {
        "field": "amount",
        "code": "OUT_OF_RANGE",
        "message": "Must be between 1 and 1000000",
        "value": 0
      }
    ],
    "request_id": "req_abc123def456",
    "documentation_url": "https://docs.api.com/errors/VALIDATION_FAILED"
  }
}

Cada error incluye:

  • code: Código máquina-parseable (los integradores pueden hacer switch/case)
  • message: Descripción humana para debugging
  • details: Array con errores de campo específicos
  • request_id: ID único para correlacionar con logs del servidor
  • documentation_url: Link directo a la documentación del error

Los códigos HTTP siguen el estándar estrictamente: 400 para errores del cliente, 401 para auth, 403 para permisos, 404 para no encontrado, 409 para conflictos, 422 para validación semántica, 429 para rate limit, 500 para errores del servidor.

Principio 6: Evoluciona sin romper

El versionado es inevitable. Tu API va a cambiar. La pregunta es cómo gestionarlo sin romper las integraciones existentes.

Nuestra estrategia de evolución:

  1. Cambios aditivos sin versión nueva: Añadir campos opcionales, nuevos endpoints, nuevos valores en enums. Estos nunca rompen clientes existentes.

  2. Breaking changes = nueva versión mayor: Eliminar campos, cambiar tipos, renombrar endpoints, modificar comportamiento. Solo en /v2/, /v3/…

  3. Periodo de deprecación de 12 meses: Cuando una versión se depreca, los consumidores tienen 12 meses para migrar. Headers Deprecation y Sunset comunican las fechas.

  4. Changelog automático: Cada deploy genera un changelog que describe qué cambió, con enlaces a documentación de migración.

  5. Feature flags, no versiones: Para funcionalidades opcionales (nuevo formato de respuesta, nuevo algoritmo de ordenación), usamos headers de opt-in en lugar de crear versiones nuevas.

En Xceed, mantenemos v1 y v2 de la API de ticketing en paralelo durante la migración de partners. Más de 200 integradores no pueden migrar de golpe.

Principio 7: Diseña para observabilidad

Cada petición genera un request_id único que viaja a través de todo el sistema (distributed tracing). Esto permite:

  • Correlacionar un error reportado por el cliente con los logs del servidor
  • Seguir una petición a través de múltiples microservicios
  • Medir la latencia de cada componente en la cadena

Además, instrumentamos cada endpoint con métricas:

  • Latencia (p50, p95, p99)
  • Tasa de error por código de estado
  • Requests por segundo
  • Tamaño de respuesta

Dashboards en Grafana con alertas automáticas cuando un endpoint degrada. En más de una ocasión, hemos detectado problemas antes de que los usuarios los reporten gracias a alertas en el p99 de latencia.

Principio 8: Protege contra ti mismo

Las APIs más peligrosas no son las atacadas por terceros, sino las que un integrador usa incorrectamente (o que tu propio frontend abusa sin darse cuenta).

Protecciones que implementamos siempre:

  • Timeouts estrictos: Cada operación tiene un timeout. Si la base de datos no responde en 5s, devolvemos 503 en lugar de bloquear el thread indefinidamente.
  • Circuit breakers: Si un servicio externo falla, dejamos de llamarlo temporalmente en lugar de acumular timeouts.
  • Request size limits: Máximo 10MB por petición. Los uploads grandes van por signed URLs directas a S3.
  • Query complexity limits: En GraphQL, queries con profundidad > 5 o coste > 1000 se rechazan automáticamente.
  • Pagination obligatoria: Los endpoints de lista no tienen opción de devolver todo. Siempre paginados.

Principio 9: Webhooks como ciudadanos de primera

En APIs modernas, los webhooks son tan importantes como los endpoints. Los integradores no deberían hacer polling para saber si algo cambió.

Nuestro diseño de webhooks incluye:

  • Retry automático con exponential backoff (hasta 72 horas de reintentos)
  • HMAC signature en cada payload para que el receptor verifique la autenticidad
  • Idempotency: El receptor puede recibir el mismo evento más de una vez (por reintentos). Incluimos un event_id único.
  • Logs de entrega: Dashboard donde el integrador ve el historial de entregas, fallos y reintentos
  • Test mode: Endpoint para simular el envío de eventos sin acciones reales

Principio 10: La documentación ES la API

Una API sin documentación no existe. Pero la documentación que se desactualiza es peor que ninguna documentación, porque genera falsa confianza.

Nuestra stack de documentación:

  • OpenAPI 3.1 como source of truth: Todo se genera desde aquí
  • Redoc o Swagger UI: Documentación interactiva auto-generada
  • Postman collection: Export automático para testing manual
  • SDKs auto-generados: TypeScript, Python, Go desde la spec OpenAPI
  • Guías de inicio rápido: En <5 minutos, el integrador hace su primera llamada
  • Changelog vivo: Se actualiza automáticamente con cada deploy

La documentación se valida en CI: si el código no coincide con la spec OpenAPI, el deploy falla. Esto garantiza que la documentación siempre está actualizada.

Conclusión: las APIs son contratos, no endpoints

Diseñar APIs que escalen no es un problema técnico exclusivamente. Es un problema de diseño de producto: tu API es un producto que otros desarrolladores consumen. La experiencia de integración determina si esos desarrolladores (internos o externos) son productivos o se frustran.

Los 10 principios que hemos descrito no son opcionales para APIs serias. Son el baseline que todo equipo debería implementar antes de exponer un endpoint al mundo. La diferencia entre una API mediocre y una API excelente no está en la tecnología subyacente, sino en la atención al detalle en el diseño del contrato, el manejo de errores, la documentación y la operación en producción.

Si quieres que revisemos la arquitectura de tu API actual o diseñemos una desde cero con estos principios, ofrecemos consultoría técnica gratuita donde analizamos tu caso y proponemos mejoras concretas.

No te pierdas nada

JM

Javier Manzano

CEO & Co-founder en Soamee

Apasionado por la tecnología y el desarrollo de software. Comparto conocimientos y experiencias para ayudar a otros desarrolladores a crecer.

¿Te ha gustado este artículo?

Si necesitas ayuda con tu proyecto de desarrollo, estamos aquí para ti.

Agenda call gratuita →