Error API 429 Too Many Requests: límites, Retry-After y backoff seguro

Guía práctica para resolver errores HTTP 429 en API de LLM: análisis de límites RPM/TPM, lectura de Retry-After e implementación de backoff con jitter.

El error HTTP 429 Too Many Requests ocurre cuando un cliente supera los límites de frecuencia o el volumen de tokens establecidos por el proveedor de la API. Ejecutar reintentos infinitos sin control no soluciona el problema, sino que agrava el bloqueo generando tormentas de reintentos (retry storms).

Para restablecer la estabilidad de su aplicación, es necesario identificar con precisión el tipo de límite (RPM, TPM o saldo agotado), analizar correctamente el encabezado Retry-After e implementar un algoritmo de retroceso exponencial con ruido aleatorio (Full Jitter Exponential Backoff).

Por qué ocurre HTTP 429: anatomía de los límites

En las API de modelos de lenguaje modernos (OpenAI, Anthropic y pasarelas compatibles), el código de estado 429 se genera mediante tres mecanismos principales:

  1. RPM (Requests Per Minute): límite en la cantidad de solicitudes HTTP por minuto. Aparece al ejecutar múltiples procesos paralelos sin colas de control.
  2. TPM (Tokens Per Minute): límite en el volumen acumulado de tokens de entrada y salida en una ventana móvil de un minuto. Se activa con frecuencia al enviar contextos extensos.
  3. Agotamiento de cuota o saldo: bloqueo debido a depósito cero, límite estricto de gasto alcanzado o finalización del saldo prepagado.
HTTP/1.1 429 Too Many Requests Date: Sun, 23 Aug 2026 03:00:00 GMT Content-Type: application/json Retry-After: 6 x-ratelimit-limit-requests: 500 x-ratelimit-remaining-requests: 0 x-ratelimit-reset-requests: 6s x-ratelimit-limit-tokens: 30000 x-ratelimit-remaining-tokens: 1200 x-ratelimit-reset-tokens: 150ms { "error": { "message": "Rate limit reached for model in organization on tokens per minute (TPM). Please try again in 6s.", "type": "tokens", "param": null, "code": "rate_limit_exceeded" } }

Cuando el error se debe a la falta de saldo o a límites rígidos de suscripción, los reintentos automáticos solo desperdician recursos de red. Para aislar de inmediato la causa sin descifrar registros complejos, BetterToken ofrece un panel de control transparente: muestra en tiempo real los estados HTTP de cada solicitud, el desglose exacto de tokens de entrada, salida y caché, y el saldo disponible bajo el modelo pay-as-you-go sin bloqueos de 5 horas.

Matriz de diagnóstico del error 429

SíntomaCausa principalInspección de respuestaSolución técnica
Fallo en ráfagas paralelasLímite RPM excedidox-ratelimit-remaining-requests: 0Limitar concurrencia con semáforos o colas
Fallo en prompts extensosLímite TPM excedidox-ratelimit-remaining-tokens < tamaño del promptOptimizar contexto, habilitar prompt caching o dividir llamadas
100% de llamadas devuelven 429Saldo / Cuota agotadaCódigo insufficient_quota o mensaje de facturaciónDetener reintentos y recargar saldo o revisar permisos
Crecimiento en avalancha de 429Tormenta de reintentosEncabezados Retry-After ignorados por los workersAñadir ruido aleatorio (Jitter) al cálculo del retardo

Cómo interpretar el encabezado Retry-After

La especificación RFC 6585 establece dos formatos válidos para el encabezado Retry-After:

  • Segundos relativos (número entero o decimal, por ejemplo Retry-After: 12);
  • Fecha HTTP (marca de tiempo GMT, por ejemplo Retry-After: Sun, 23 Aug 2026 03:05:00 GMT).
import datetime import email.utils import time def parse_retry_after(header_value: str | None, default_delay: float = 1.0) -> float: if not header_value: return default_delay header_value = header_value.strip() try: return max(0.0, float(header_value)) except ValueError: pass try: parsed_date = email.utils.parsedate_to_datetime(header_value) now = datetime.datetime.now(datetime.timezone.utc) delay = (parsed_date - now).total_seconds() return max(0.0, delay) except Exception: return default_delay

Implementación de Full Jitter Exponential Backoff

Si no existe el encabezado Retry-After, la solución estándar es el retroceso exponencial con variación aleatoria total (Full Jitter). La fórmula para el intento ii es:

Textwait=extrandom(0,min(Textmax,Textbaseimes2i))T_{ ext{wait}} = ext{random}(0, \min(T_{ ext{max}}, T_{ ext{base}} imes 2^i))

import asyncio import json import random import httpx class RateLimitRetryClient: def __init__( self, base_url: str = "https://www.bettertoken.ai/v1", api_key: str = "", max_retries: int = 4, base_delay: float = 1.0, max_delay: float = 32.0, ): self.base_url = base_url self.api_key = api_key self.max_retries = max_retries self.base_delay = base_delay self.max_delay = max_delay self.client = httpx.AsyncClient( base_url=self.base_url, headers={"Authorization": f"Bearer {self.api_key}"}, timeout=60.0, ) async def send_chat_completion(self, payload: dict) -> dict: for attempt in range(self.max_retries + 1): try: response = await self.client.post("/chat/completions", json=payload) if response.status_code == 200: return response.json() if response.status_code == 429: error_data = response.json().get("error", {}) error_code = error_data.get("code") if error_code in ("insufficient_quota", "billing_not_active"): raise RuntimeError(f"Error de facturación: {error_data.get('message')}") if attempt == self.max_retries: raise RuntimeError(f"Límite de reintentos agotado (429): {response.text}") retry_after = response.headers.get("Retry-After") if retry_after: wait_time = parse_retry_after(retry_after) + random.uniform(0.1, 0.5) else: backoff_cap = min(self.max_delay, self.base_delay * (2 ** attempt)) wait_time = random.uniform(0, backoff_cap) await asyncio.sleep(wait_time) continue response.raise_for_status() except httpx.RequestError as exc: if attempt == self.max_retries: raise wait_time = min(self.max_delay, self.base_delay * (2 ** attempt)) await asyncio.sleep(wait_time) raise RuntimeError("La solicitud falló tras agotar el presupuesto de reintentos")

Idempotencia y seguridad en reintentos

Reintentar operaciones de lectura (GET) es seguro. Sin embargo, al invocar inferencia de LLM mediante POST:

  1. Evite duplicar tareas generativas: si una solicitud se interrumpe por tiempo de espera, verifique si se consumieron tokens antes de volver a enviarla.
  2. Utilice identificadores de cliente: incluya encabezados X-Request-ID únicos para auditar llamadas en los registros.
  3. No trate errores 401 o 403 como 429: los errores de autenticación requieren corregir las credenciales, no aplicar pausas.

Validación y recuperación del servicio

Antes de restaurar el tráfico masivo:

  • Envíe una solicitud de prueba mínima (max_tokens: 5).
  • Compruebe la recepción de HTTP 200 y el encabezado x-ratelimit-remaining-requests.
  • Aumente la concurrencia gradualmente mientras supervisa la tasa de errores 429 en sus métricas.

Para evitar bloqueos 429 inesperados debidos a límites estrictos por minuto y supervisar de forma transparente el estado de cada llamada, acceda a BetterToken API, genere claves de API dedicadas y controle el consumo de tokens en tiempo real desde el panel de control.

¿Quieres optimizar tu flujo de trabajo con LLM?

Conecta modelos mediante una API, gestiona claves y controla el gasto en IA.