API Rate Limiting429 Error HandlingProduction Reliability

Cómo manejar límites de tasa y errores 429 en LLM APIs

1 min de lectura

Un 429 Too Many Requests es la respuesta más útil que envía tu proveedor de LLM. Más útil que el cuerpo JSON. Más accionable que el código de error. Porque escondido en sus headers hay un medidor de capacidad en tiempo real —x-ratelimit-remaining, retry-after— que la mayoría del código de producción ignora. Los ingenieros tratan los límites de tasa como una pared contra la que chocar. Son un medidor que hay que leer.

Así es chocar contra la pared. Tu app funciona perfectamente a las 2 PM. El tráfico sube a las 3 PM. A las 3:15, cada request devuelve 429. Tu lógica de reintento se dispara —intervalos fijos de un segundo— y crea una manada atronadora (thundering herd). Cada reintento golpea la misma ventana de límite. Diez minutos de fallos en cascada. Los usuarios ven errores. Le avisan al de guardia. La solución no era “reintentar con más fuerza”. Nunca fue reintentar con más fuerza.

La brecha entre esos dos resultados —chocar contra la pared versus leer el medidor— son tres capas de código. Este artículo cubre las tres. La capa reactiva: backoff exponencial con jitter. La capa proactiva: throttling consciente de headers que lee tu presupuesto restante y frena antes de llegar a la pared. La capa predictiva: suspensión antes de la llamada que hace checkpoint del estado del agente antes de que la siguiente llamada agote tu cuota. Código Python funcional para cada capa. Patrones probados en producción.

Para los conceptos fundamentales de autenticación de API y gestión de claves que sustentan el manejo de límites de tasa, consulta nuestra guía de seguridad de API keys.

Por qué existen los límites de tasa —y cómo funcionan realmente

Los límites de tasa no son un castigo. Son protección de infraestructura. Cada request de API consume memoria GPU y cómputo. Un cliente sin throttling puede saturar el clúster de inferencia de un proveedor en segundos. Los límites aseguran una asignación justa entre todos los usuarios.

Los tres límites que debes cuidar:

  • RPM (Requests Per Minute): cuántas llamadas API puedes hacer. Planes de pago por uso: típicamente 500–3,000 RPM. Niveles gratis: 10–50 RPM. Empresarial: personalizado.
  • TPM (Tokens Per Minute): tokens totales de todos los requests —entrada + salida. Un solo prompt de 100K tokens consume tanta cuota como 500 requests normales. El límite de TPM protege contra esto.
  • Requests concurrentes: cuántos requests pueden estar en curso simultáneamente. Excede esto y los nuevos requests se ponen en cola o se rechazan. Este límite suele no estar documentado y se aprende por experiencia dolorosa.

Los niveles específicos de cada proveedor ponen números reales a estos límites. El Tier 5 de OpenAI (el nivel más alto de pago por uso) otorga 10,000 RPM y 30,000,000 TPM para modelos GPT-4.x —pero el Tier 1 empieza con solo 500 RPM y 200,000 TPM. La Claude API de Anthropic ofrece 1,000 RPM en su nivel estándar con 80,000 TPM para Claude Opus y 400,000 TPM para Claude Sonnet, reflejando diferentes costos de inferencia por modelo.

La Gemini API de Google ofrece 1,500 RPM en pago por uso con un tope de 2,000,000 TPM. Cada proveedor también aplica anulaciones por modelo —el límite de TPM de Claude Opus 4 es más estricto que el de Claude Sonnet 4 porque los modelos más grandes consumen proporcionalmente más cómputo. Pasar del Tier 1 al Tier 5 en OpenAI requiere tanto un historial de gasto aumentado ($250+ al mes) como un historial demostrado de uso sin abusos durante 30+ días.

No consigues límites altos pidiéndolo amablemente —los ganas con patrones sostenidos de tráfico de producción que demuestran que no inundarás el clúster. Conocer tu nivel exacto y sus límites es el primer paso para construir cualquiera de las tres capas siguientes.

Cómo leer tus límites actuales. Cada respuesta de API incluye headers de límite de tasa —y casi nadie los lee. La documentación de límites de tasa de OpenAI explica el formato de headers y la estructura de niveles:

x-ratelimit-remaining-requests: 487
x-ratelimit-remaining-tokens: 823000
x-ratelimit-reset-requests: 12s

Estos vienen en respuestas 200, no solo en 429. Te dicen exactamente cuánto presupuesto queda antes de que te hagan throttling. Muéstralos como un medidor en tu dashboard de monitoreo. Alerta cuando el remanente baje del 20%. La diferencia entre “chocamos con un límite” y “lo vimos venir y lo esquivamos” es leer estos headers.

Historias de terror con límites de tasa: dos incidentes que no quieres repetir

Una plataforma europea de e-commerce lanzó un asistente de compras con IA para el Black Friday impulsado por GPT-4.5. Su QA probó con 50 usuarios concurrentes —producción llegó a 2,300 en la primera hora. Los reintentos de intervalo fijo convirtieron los 429 en una caída de 47 minutos.

Pérdida de ingresos: $180,000 en abandono de carrito rastreado durante esa ventana. La causa raíz no fue el volumen de tráfico —fue la lógica de reintentos que amplificó el pico en lugar de absorberlo.

Una empresa SaaS de analítica migró entre versiones de la API de OpenAI sin leer el changelog de límites. La nueva versión redujo a la mitad su RPM de 3,000 a 1,500 en su nivel. Su código de throttling existente asumía el límite anterior.

Producción funcionó bien durante seis días —hasta que un ciclo mensual de reportes triplicó su volumen de requests. Cada trabajo de reporte chocó con 429 simultáneamente. La detección tomó 22 minutos porque su monitoreo solo rastreaba errores 5xx, no 429.

La solución fue de tres líneas: actualizar la constante RPM. La lección fue permanente: cada migración de versión de API es una migración de límites de tasa.

Capa 1: Throttling del lado del cliente

La capa más simple. Un token bucket o semáforo que evita que tu aplicación supere los límites declarados por el proveedor.

import asyncio
import time

class RateLimiter:
    """Token bucket rate limiter for LLM API calls."""

    def __init__(self, max_rpm: int):
        self.max_rpm = max_rpm
        self.tokens = max_rpm
        self.last_refill = time.monotonic()
        self.semaphore = asyncio.Semaphore(max_rpm // 6)  # Concurrency cap

    async def acquire(self):
        """Wait until a request can be sent without exceeding RPM."""
        # Refill tokens based on elapsed time
        now = time.monotonic()
        elapsed = now - self.last_refill
        refill = elapsed * (self.max_rpm / 60)
        self.tokens = min(self.max_rpm, self.tokens + refill)
        self.last_refill = now

        if self.tokens < 1:
            wait_time = (1 - self.tokens) / (self.max_rpm / 60)
            await asyncio.sleep(wait_time)
            self.tokens = 1

        self.tokens -= 1

limiter = RateLimiter(max_rpm=500)

async def rate_limited_api_call(model: str, messages: list):
    await limiter.acquire()
    # Make the API call

Orden importante: siempre adquiere el token RPM antes del semáforo de concurrencia. Invertir el orden causa bloqueo de cabeza de línea —los slots de concurrencia se llenan con requests que no pueden enviarse, dejando hambrientos a los que sí podrían.

Esta capa previene la herida autoinfligida más común de límites: exceder el límite declarado de tu nivel porque no rastreaste qué tan rápido llamabas. Para la mayoría de las aplicaciones de tráfico bajo a moderado, esto es suficiente.

Capa 2: Backoff consciente de headers

La capa 1 previene que superes límites conocidos. La capa 2 maneja lo que pasa cuando la capacidad real del proveedor fluctúa —y fluctúa constantemente, según la carga general del clúster.

import random
from openai import OpenAI

client = OpenAI(
    base_url="https://api.tokspan.com/v1",
    api_key="ts-your-key-here"
)

def chat_with_backoff(messages, model="claude-opus-4-8", max_retries=4):
    """Exponential backoff with jitter + header awareness."""
    for attempt in range(max_retries):
        try:
            response = client.chat.completions.create(
                model=model,
                messages=messages,
                timeout=30
            )
            # Read remaining budget from headers —even on success
            remaining = response.headers.get("x-ratelimit-remaining-requests")
            if remaining and int(remaining) < 50:
                print(f"Rate limit low: {remaining} requests remaining. Slow down.")
            return response

        except Exception as e:
            if "429" in str(e) or "rate_limit" in str(e).lower():
                if attempt == max_retries - 1:
                    raise  # Out of retries
                # Exponential backoff: 1s —2s —4s —8s
                wait = (2 ** attempt) + random.uniform(0, 1)
                print(f"Rate limited. Retrying in {wait:.1f}s (attempt {attempt + 1}/{max_retries})")
                import time
                time.sleep(wait)
            else:
                raise  # Not a rate limit error —don't retry

Las tres reglas del backoff:

  1. Nunca reintentes con intervalo fijo. Crea manadas atronadoras. Cada cliente que reintenta en t+1s golpea la misma ventana de límite.
  2. Siempre agrega jitter. + random.uniform(0, 1) distribuye los reintentos a través de la ventana. Solo esto previene la mayoría de los fallos en cascada.
  3. Nunca reintentes 401, 403 o 400. Reintentar una API key mala o un request mal formado no lo arreglará. Solo reintenta 429 y 5xx.

Qué NO hacer. Este patrón —sorprendentemente común en código de producción— es el equivalente de límites a gritarle más fuerte a alguien que no habla tu idioma:

# DO NOT DO THIS
while True:
    try:
        response = client.chat.completions.create(...)
        break
    except:
        time.sleep(1)  # Fixed interval, no jitter, infinite retry

Esto crea una manada atronadora y garantiza que te quedes con límite de tasa. Cada reintento llega exactamente al mismo punto de la ventana. La infraestructura del proveedor ve un pico de requests idénticos, les hace throttling a todos, y tu app entra en una espiral de muerte.

Capa 3: Suspensión predictiva

Las capas 1 y 2 son reactivas —responden después de golpear (o acercarse a) un límite. La capa 3 es predictiva —lee el presupuesto antes de la llamada y decide: continuar, esperar brevemente, o hacer checkpoint y suspender.

def predict_rate_limit(response_headers: dict) -> str:
    """Three-valued decision based on remaining budget."""
    remaining_req = int(response_headers.get("x-ratelimit-remaining-requests", 1000))
    remaining_tok = int(response_headers.get("x-ratelimit-remaining-tokens", 1000000))

    if remaining_req > 100 and remaining_tok > 200000:
        return "continue"      # Plenty of budget
    elif remaining_req > 20:
        return "wait"          # Budget running low —short pause
    else:
        return "checkpoint"    # Budget nearly exhausted —suspend

# Usage in an agent loop:
for step in agent_steps:
    response = call_llm(current_state)
    decision = predict_rate_limit(response.headers)

    if decision == "continue":
        process(response)
    elif decision == "wait":
        time.sleep(5)  # Short pause, let budget recover
        process(response)
    else:  # checkpoint
        save_agent_state(current_state)  # Save progress
        time.sleep(60)  # Wait for rate-limit window reset
        resume_agent_from_checkpoint()  # Resume without losing work

El estado del arte en 2026: agentpause. Una librería Python que lee los headers de límite en cada respuesta, predice el agotamiento antes de la siguiente llamada y hace checkpoint del estado del agente antes de suspender. Resultados medidos: 0% de tasa de crash vs. 100% de línea base reactiva. Cero errores 429. 80% menos desperdicio de tokens por reintentos fallidos. Si ejecutas agentes de producción de alto rendimiento, agentpause o la lógica predictiva equivalente ya no es opcional —es la diferencia entre “nuestros agentes son confiables” y “nuestros agentes se caen al azar cuando el tráfico se dispara”.

Enrutamiento multi-proveedor: la mejor solución de límites de tasa

Cada estrategia anterior asume que hablas con un proveedor. La estrategia de límites más efectiva es hablar con varios.

La idea central: en lugar de esperar a que se resetee el límite de un proveedor, envía el request a otro proveedor. Tu límite efectivo se convierte en la suma de todos los proveedores a los que puedes enrutar.

Un router round-robin con token buckets por proveedor y cooldown automático:

PROVIDERS = {
    "openai": {"rpm": 2000, "cooldown_until": 0},
    "anthropic": {"rpm": 1500, "cooldown_until": 0},
    "google": {"rpm": 1000, "cooldown_until": 0},
}

def route_request(messages):
    now = time.time()
    available = [
        p for p, cfg in PROVIDERS.items()
        if now > cfg["cooldown_until"]
    ]
    if not available:
        raise RuntimeError("All providers in cooldown")

    # Round-robin among available providers
    provider = available[hash(str(messages)) % len(available)]
    try:
        return call_provider(provider, messages)
    except RateLimitError:
        PROVIDERS[provider]["cooldown_until"] = now + 30  # Cooldown for 30s
        return route_request(messages)  # Retry with a different provider

Las plataformas de agregación manejan esto a nivel de infraestructura —tu endpoint único enruta a todos los proveedores, con cooldown automático, failover y monitoreo de límites. Tú fijas tu throughput deseado. La plataforma gestiona los límites por proveedor. Para la implementación completa de enrutamiento y failover consciente de latencia, consulta nuestra guía de configuración multi-modelo de producción y la documentación de enrutamiento personalizado.

FAQ

¿Cuál es el error de límites de tasa más común?

El reintento de intervalo fijo. Los retrasos de un segundo en todos los clientes crean una manada atronadora que garantiza más 429. Usa siempre backoff exponencial con jitter aleatorio. Solo el jitter —agregar random.uniform(0, 1) a tu tiempo de espera— previene la mayoría de los fallos en cascada.

¿Cómo sé cuáles son mis límites de tasa?

Revisa el dashboard de tu proveedor para los límites declarados de tu nivel. Luego lee los headers x-ratelimit-remaining-* en cada respuesta de API —te dicen tu presupuesto restante real en tiempo real. Monitorea. No conoces tus límites hasta que los golpeas —y así es como opera la mayoría de los equipos.

¿Usar múltiples proveedores realmente resuelve los límites de tasa?

Sí —efectivamente. Un límite de 500 RPM por proveedor se convierte en 2,000 RPM en cuatro proveedores con un router round-robin. Las plataformas de agregación con enrutamiento multi-proveedor hacen esto transparente: un endpoint, gestión automática de límites a nivel de proveedor. Combina el enrutamiento multi-proveedor con estrategias de optimización de costos para aumentar el throughput sin duplicar tu factura —enruta modelos más baratos para requests no críticos y reserva los caros para tareas que los necesitan.

¿Cuál es la solución más simple que puedo implementar hoy?

Reemplaza tu reintento de intervalo fijo por backoff exponencial + jitter. Cinco líneas de código. Previene la manada atronadora que convierte un solo 429 en una caída en cascada. El código de la Capa 2 de este artículo está listo para copiar y pegar.

¿Pueden las plataformas de agregación manejar los límites de tasa por mí?

Sí. El enrutamiento multi-proveedor, el cooldown automático para proveedores con throttling y los dashboards unificados de límites son características estándar. Configuras tu throughput deseado. La plataforma maneja la gestión de cuotas por proveedor, el monitoreo de headers y el failover automático. Un endpoint. Sin 429.

Las tres capas aquí —throttle, backoff, predict— convierten los límites de tasa de una amenaza de confiabilidad en un problema de ingeniería resuelto. Pero a medida que el enrutamiento multi-proveedor se vuelve estándar y los proveedores compiten por garantías de throughput, la pregunta cambia: ¿se unirá “límite de tasa excedido” a “disco lleno” y “memoria insuficiente” como errores que la infraestructura moderna simplemente vuelve obsoletos? Por ahora, el código de este artículo te mantiene corriendo. El arco más largo apunta a algo más interesante.

La Capa 1 y la Capa 2 puedes implementarlas en una tarde con el código de arriba. La Capa 3 —suspensión predictiva— requiere más inversión. Las plataformas de agregación agrupan las tres en la ruta del request: el enrutamiento multi-proveedor absorbe el throttling a nivel de proveedor, el monitoreo de headers alimenta un dashboard compartido de límites, y el cooldown automático mantiene a los proveedores sanos en rotación cuando uno se degrada. Ninguna arquitectura elimina los 429 por completo, pero repartir el tráfico entre proveedores y leer los headers antes de golpear la pared los convierte de un incidente semanal en un caso límite raro.