Por qué el manejo de rate limits en el cliente importa más de lo que crees

Si construyes algo que habla con una API de terceros, probablemente te has topado con un 429 Too Many Requests en el peor momento. Tal vez un cliente de pago está mirando un spinner que no termina. Tal vez tu tarea en segundo plano quemó silenciosamente su cuota y falló dos veces. Tal vez una cadena de cron se despertó en el mismo segundo y tumbó el servicio para todos.

La buena noticia: la mayor parte del dolor por rate limits se resuelve del lado del cliente, sin rediseñar nada. Puedes instalar hábitos en tu stack que detecten cuando un límite se acerca, cedan el paso con educación y eviten arrollar al proveedor. Esta guía recorre qué buscar en la respuesta, cómo reintentar bien y cómo evitar que tus reintentos provoquen el próximo corte.

Un aviso antes de empezar: este artículo trata sobre cómo se comporta tu código cuando llama a la API de otro. No cubre cómo diseñar un limitador para tu propio servidor. Si eres tú quien expone la API, es otro problema y otro conjunto de herramientas.

Las dos familias de cabeceras que verás en la práctica

Las APIs del mundo real no se ponen de acuerdo en cómo decirte “más despacio.” En la práctica verás dos familias. Trata a las dos como parte del contrato.

1. La clásica cabecera Retry-After

Es la cabecera más antigua y mejor soportada. Aparece en respuestas 429 Too Many Requests (y a veces en 503). Su valor es simple: cuántos segundos esperar, o una fecha HTTP a partir de la cual puedes volver a intentar.

  • Retry-After: 30 significa espera 30 segundos antes del próximo intento.
  • Retry-After: Wed, 21 Oct 2026 07:28:00 GMT significa espera hasta ese instante exacto.

La documentación de OpenAPI de Speakeasy trata a Retry-After como la compañera natural del 429, y el maintainer de Ky (cliente HTTP para JavaScript), en una discusión en Hacker News sobre el nuevo borrador del IETF, recomienda que quien diseña APIs “use Retry-After todo lo que pueda y solo implemente las cabeceras de rate limit cuando sea realmente necesario.” Coincide con lo que verás en producción: proveedores bien gestionados como Stripe, GitHub o Twilio envían Retry-After de forma consistente.

2. La familia más nueva RateLimit y RateLimit-Policy

El IETF lleva tiempo trabajando en un conjunto de cabeceras más estructurado. El borrador actual, draft-ietf-httpapi-ratelimit-headers-11, define dos campos:

  • RateLimit-Policy: una descripción estructurada de la política vigente: nombre, clave de partición, cuota q, ventana w en segundos y unidades de cuota.
  • RateLimit: la política aplicada a tu petición y lo que queda: cuota disponible r y ventana efectiva t.

El trío anterior más simple — RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset — está documentado en http.dev, que apunta a que servicios como GitLab, CircleCI y OKX ya lo envían, y que sustituye a las cabeceras X-RateLimit-* que muchas APIs todavía usan.

Las cabeceras no estándar antiguas (X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset) siguen siendo habituales. Si tu SDK cubre a un proveedor, probablemente tendrá que detectar varias variantes.

Por qué esto te afecta

Si solo reaccionas a los 429 después de que ocurren, gastas dinero en llamadas fallidas y frustras a usuarios. Las cabeceras nuevas te permiten ver un límite acercarse en una respuesta 200 OK normal y aminorar antes de que te bloqueen. Esa es la diferencia entre “reintentar tras un fallo” y “modelar el tráfico para que los fallos casi no ocurran.”

Leer las cabeceras sin montarte un parser a mano

Casi nunca necesitas escribir un parser de structured fields. Los valores son lo bastante simples para tratarlos con cuidado.

  • Un entero suelto es la cuota restante. RateLimit-Remaining: 42 significa 42 llamadas en esta ventana.
  • Una ventana como w=60 significa 60 segundos.
  • Pueden aparecer varias políticas separadas por comas; trata cada una como un presupuesto independiente.
  • Las claves de partición (pk) son pistas que el servidor te da sobre en qué cubo estás — por usuario, por app, por endpoint, o alguna combinación. Trátalas como tokens opacos; el servidor debería documentar cómo las genera para que puedas predecirlas.

Un atajo práctico: casi todos los SDKs por los que realmente pagarías (Ky, undici, AWS SDK, Stripe SDK, OpenAI SDK) ya parsean esto por ti. Antes de construir nada a medida, comprueba si tu cliente HTTP o SDK expone un hook para callbacks de rate limit. Si lo hace, conéctate a él y ahórrate mantenimiento.

Cómo reintentar bien: cuatro hábitos que previenen el 90% del dolor

La lógica de reintentos es donde el manejo de rate limits brilla o se rompe. Cuatro hábitos cubren casi todos los casos.

1. Honra Retry-After primero

Si el servidor te dice cuándo reintentar, esa es la mejor señal. Prefiérala siempre sobre cualquier cálculo tuyo. Es el cambio con más impacto, porque el proveedor conoce su propio estado mejor que tú.

Si Retry-After no viene en el 429, recae en un valor prudente. Muchas APIs usan entre 1 y 5 segundos como punto de partida educado. Elige un número conservador y documéntalo; no elijas cero.

2. Backoff exponencial para fallos repetidos

Cuando sigues recibiendo 429 después de reintentar — o cuando te topas con errores 5xx transitorios — cede exponencialmente. Un patrón habitual: 1s, 2s, 4s, 8s, con tope entre 30s y 60s. Eso le da al proveedor espacio para recuperarse y evita que sigas golpeando un servicio que ya está sufriendo.

3. Añade siempre jitter

Esto es lo que muchos fundadores olvidan, y es la causa de la mayoría de los cortes del tipo “todos los reintentos saltan exactamente en el mismo instante.” Si 50 tareas en segundo plano reintentan tras 4 segundos, acabas de crear un pico nuevo.

Jitter significa aleatorizar cada reintento dentro de una ventana: en vez de esperar exactamente 4 segundos, espera entre 3 y 5. El jitter completo — elegir un valor uniforme aleatorio hasta el tope — es el patrón más seguro y lo que recomienda el maintainer de Ky para “clientes buenos.”

4. Pon un tope al número total de reintentos

Los reintentos sin tope son cómo una caída de 10 minutos se convierte en una factura de 3 horas. Decide de antemano:

  • Un número máximo de intentos (a menudo 3 a 5).
  • Un tiempo total máximo de reloj que estás dispuesto a esperar.
  • Un modo de fallo claro cuando te rindes — muestra el error al usuario, encola la tarea para más tarde, o sáltate el elemento, según lo que haga la llamada.

Evitar la estampida (thundering herd)

La estampida ocurre cuando muchos clientes reintentan a la vez tras un fallo compartido. Es una de las formas más comunes en que un error pequeño en el cliente tumba un servicio.

La mitigación es básicamente el hábito de jitter de arriba, pero merece la pena entender los patrones que crean el problema:

  • Tareas cron sincronizadas. Si la tarea programada de cada cliente se dispara a medianoche UTC, todos reintentarán en el mismo segundo. Escalona las programaciones por cliente o con un offset aleatorio.
  • Workers que despiertan de una pausa en la cola. Si tu cola pausó 60 segundos y reinicias 500 workers a la vez, golpearán la API en el mismo tick. Usa un retraso de arranque con jitter.
  • Reintentos en cascada desde una dependencia compartida. Cuando un proveedor aguas arriba se recupera, todos los llamantes aguas abajo reintentan a la vez. El jitter en el cliente es la única mitigación realista; el “decorrelated jitter” funciona mejor.
  • Tormentas de reconexión. Clientes que se desconectaron durante un corte se reconectan juntos. Mismo arreglo.

Si operas cualquier tipo de cola, flota de workers o sistema de tareas programadas, integra jitter y escalonado una sola vez, a nivel de framework. Rehacerlo después es doloroso.

Cómo es un cliente HTTP o SDK “bien hecho”

Cuando evalúes un cliente HTTP, SDK o plataforma de integración, las funciones de rate limit que realmente importan son:

  • Detección de cabeceras lista para usar. ¿Parsea tanto Retry-After como la familia RateLimit? ¿Expone los valores parseados para que puedas registrar o actuar sobre ellos?
  • Backoff y jitter integrados. O al menos un hook limpio para que no tengas que escribirlo tú.
  • Presupuestos por política. ¿Puede seguir más de una política a la vez (algunas APIs aplican límites distintos por endpoint o partición)?
  • Hooks de observabilidad. ¿Emite eventos cuando te acercas a un límite, recibes un 429 o cedes? Así es como se depuran incidentes sin buscar en logs a las 2 de la mañana.
  • Topes de reintento. ¿Te deja fijar un número máximo de intentos y un presupuesto total?

Para integraciones multi-proveedor (un cliente hablando con muchas APIs con reglas distintas), plataformas como Speakeasy, Unify o Apideck se ganan su sitio precisamente porque centralizan esta lógica. Si cableas cinco APIs externas a mano, espera mantener cinco estrategias de reintento ligeramente distintas.

Lista de comprobación inicial

Antes de lanzar la próxima integración:

  1. Confirma qué cabeceras envía cada proveedor. Lee la documentación y luego comprueba con un curl -i real.
  2. Asegúrate de que tu cliente honra Retry-After antes que cualquier otra cosa.
  3. Añade backoff exponencial con jitter como camino de respaldo.
  4. Pon tope de reintentos por número de intentos y por tiempo total.
  5. Si manejas colas, schedulers o pools de workers, añade arranque escalonado y jitter por tarea.
  6. Registra las cabeceras que recibes cuando te llega un 429 — son oro para depurar incidentes del proveedor.
  7. Prueba tu lógica de reintento simulando un 429 desde un servidor mock. Si no puedes probarla fácil, no la controlas.

Preguntas frecuentes

¿Necesito soportar las cabeceras nuevas RateLimit, o basta con Retry-After? Para la mayoría de integraciones hoy, basta con Retry-After. El maintainer de Ky y varios profesionales en Hacker News dijeron lo mismo: envía Retry-After primero, añade RateLimit solo cuando tengas una razón real. Las cabeceras estructuradas importan sobre todo cuando quieres modelar el tráfico de forma proactiva en vez de reactiva.

¿Qué estrategia de jitter uso? Jitter completo (un retardo uniforme aleatorio hasta el tope) es la opción segura por defecto. Es el patrón recomendado en la propia discusión del borrador del IETF sobre reintentos y en SDKs en producción. Evita el “decorrelated” o el “equal” jitter a menos que hayas medido una razón para preferirlos.

¿Cuántos reintentos son razonables? Tres a cinco intentos en total es un rango habitual, con un techo firme de tiempo total. Más allá de eso, normalmente estás tapando un problema real.

¿Reintento los 5xx igual que los 429? A menudo sí, con el mismo backoff y jitter, pero nunca reintentes 4xx que no sean 429. Un 401, 403, 404 o 422 no mejoran por repetir.

Fuentes

  • Borrador del IETF: draft-ietf-httpapi-ratelimit-headers-11 (datatracker.ietf.org)
  • Repositorio del grupo de trabajo del IETF: github.com/ietf-wg-httpapi/ratelimit-headers
  • Documentación OpenAPI de Speakeasy sobre respuestas de rate limiting: speakeasy.com/openapi/responses/rate-limiting
  • Discusión en Hacker News sobre cabeceras HTTP RateLimit: news.ycombinator.com/item?id=46618105
  • Guía de http.dev sobre la cabecera RateLimit-Limit: http.dev/ratelimit-limit
  • Blog de Tony Finch: HTTP RateLimit Headers (dotat.at/@/2026-01-13-http-ratelimit.html)

Sources