429 Too Many Requests de una API de IA: reintentos con backoff
Qué significa un 429 de una API de IA y cómo manejarlo: backoff exponencial con jitter, Retry-After y concurrencia de agentes, con código en Python y JavaScript.

En esta página
Un 429 Too Many Requests de una API de IA significa que la solicitud fue rechazada porque enviaste demasiado demasiado rápido, o alcanzaste un límite en tu cuenta. La solución es esperar y reintentar con backoff exponencial y jitter, usar el encabezado Retry-After cuando la respuesta lo incluya, y limitar cuántas solicitudes envía tu agente en paralelo. No reintentes un 429 que en realidad es un límite de gasto o de saldo: esperar no lo solucionará.
Esta guía se basa en las guías oficiales de límites de tasa de OpenAI y Anthropic, ambas consultadas el 22 de octubre de 2026. Ninguna de las dos páginas muestra una fecha de última actualización.
Qué significa un 429#
OpenAI describe un 429 como una solicitud rechazada por exceder un límite de tasa (rate_limit_error, con el código slow_down cuando el tráfico aumenta demasiado rápido), o porque se alcanzó un límite de gasto estricto. Anthropic también usa rate_limit_error para 429. Un 429 por tope de gasto no tiene encabezado retry-after y sigue fallando hasta que se reanude el acceso.
Hay dos vecinos fáciles de confundir con él. El 503 server_ de OpenAI y el 529 overloaded_ de Anthropic son condiciones de sobrecarga, no tu límite de tasa. Normalmente se manejan con el mismo bucle de reintentos, pero indican algo distinto sobre la causa.
Así que hay tres casos, y cada uno requiere una reacción diferente:
Desplaza la tabla hacia la derecha →
| Respuesta | Causa | Qué hacer |
|---|---|---|
| 429 límite de tasa | Demasiadas solicitudes o tokens por minuto | Esperar, reintentar con backoff |
| 429 límite de gasto o saldo | Límite de cuenta alcanzado | No reintentar; ajustar el límite o recargar el saldo |
| 503 / 529 sobrecargado | El lado del proveedor está ocupado | Reintentar con backoff, con un tope |
Si tu saldo se agotó, lee el error "not enough tokens" en su lugar. Para fallos a nivel de red como timeouts y conexiones rechazadas, consulta errores de conexión comunes.
Lee los encabezados antes de adivinar un retraso#
OpenAI documenta estos encabezados en respuestas limitadas por tasa: Retry-After, y x-, x-, x- con variantes -tokens. Otras API pueden enviar un subconjunto o ninguno de ellos, así que escribe el cliente para usar Retry-After cuando esté presente y recurrir a tu propio esquema cuando no lo esté.
La guía de OpenAI sobre reintentos, en resumen:
- Trata
Retry-Aftercomo la espera mínima y añade un pequeño retraso aleatorio encima. - Sin él, usa backoff exponencial con jitter.
- Limita tanto el número de intentos como el tiempo total de reintentos.
- No apiles tus propios reintentos sobre los reintentos del SDK.
- No reintentes errores de cuota o facturación.
- Las solicitudes fallidas también cuentan para el límite por minuto, así que un bucle agresivo empeora las cosas.
- Para streaming, no repitas una solicitud después de haber consumido ya salida de ella.
Python: backoff con jitter#
El paquete oficial openai funciona con cualquier endpoint compatible con OpenAI. Desactiva los reintentos propios del SDK (max_retries=0) para que no se multipliquen con los tuyos. El id del modelo proviene de la documentación de CheapTokens; los ids difieren entre proveedores.
import os
import random
import time
from openai import OpenAI, APIStatusError
client = OpenAI(
base_url="https://api.cheaptokens.dev/v1",
api_key=os.environ["CHEAPTOKENS_API_KEY"],
max_retries=0, # we retry ourselves
)
RETRYABLE = {429, 500, 502, 503, 504}
def chat_with_retry(model, messages, max_attempts=5, max_total=60.0):
started = time.monotonic()
for attempt in range(max_attempts):
try:
return client.chat.completions.create(model=model, messages=messages)
except APIStatusError as err:
last = attempt == max_attempts - 1
if err.status_code not in RETRYABLE or last:
raise
header = err.response.headers.get("retry-after")
if header and header.replace(".", "", 1).isdigit():
delay = float(header) + random.uniform(0, 1) # minimum wait + jitter
else:
delay = random.uniform(0, min(30.0, 2.0 ** attempt)) # full jitter
if time.monotonic() - started + delay > max_total:
raise
time.sleep(delay)El límite superior del retraso sin encabezado crece 1, 2, 4, 8 segundos (con tope en 30), y random.uniform elige un valor aleatorio por debajo de él, para que muchos workers no se despierten todos en el mismo momento. Retry-After también puede ser una fecha HTTP; el código anterior maneja la forma numérica y, en caso contrario, recurre al backoff.
JavaScript: el mismo bucle#
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.cheaptokens.dev/v1",
apiKey: process.env.CHEAPTOKENS_API_KEY,
maxRetries: 0, // we retry ourselves
});
const RETRYABLE = new Set([429, 500, 502, 503, 504]);
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
export async function chatWithRetry(model, messages, maxAttempts = 5, maxTotalMs = 60000) {
const started = Date.now();
for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
return await client.chat.completions.create({ model, messages });
} catch (err) {
const last = attempt === maxAttempts - 1;
if (!RETRYABLE.has(err.status) || last) throw err;
const header = Number(err.headers?.get?.("retry-after"));
const delayMs = Number.isFinite(header) && header > 0
? header * 1000 + Math.random() * 1000 // minimum wait + jitter
: Math.random() * Math.min(30000, 2 ** attempt * 1000); // full jitter
if (Date.now() - started + delayMs > maxTotalMs) throw err;
await sleep(delayMs);
}
}
}Cuando funciona, una llamada limitada por tasa simplemente tarda más y luego devuelve una finalización normal. Cuando sigue fallando tras el último intento, se lanza el error original, para que tu código pueda registrarlo o encolar el trabajo para más tarde.
Cómo deben manejar los agentes un 429#
Un bucle de agente envía muchas llamadas cortas, y varios agentes o llamadas de herramientas pueden ejecutarse a la vez. Reintentar por sí solo no basta; reduce la tasa de solicitudes en el origen:
- Limita la concurrencia. Un semáforo de 3–5 llamadas en paralelo es un comienzo sensato; auméntalo solo mientras los errores sigan siendo raros.
- Comparte un único limitador. Si varios workers usan la misma clave, comparten los mismos límites, así que un bucle de reintentos por worker no basta: usa una cola o un semáforo para el proceso.
- Aplica backoff globalmente. Cuando una llamada recibe un 429 con un retraso, pausa todo el pool durante ese tiempo en lugar de solo la llamada que falla.
- Mantén las solicitudes más pequeñas. Menos tokens por solicitud significa menos presión sobre los límites de tokens por minuto: recorta el historial y la salida de herramientas. Consulta cómo estimar el coste de un agente de IA para saber adónde van los tokens.
- Detente ante errores no reintentables. Un 401, un 400 o un error de saldo no se arreglarán solos; falla rápido e informa.
import asyncio
limit = asyncio.Semaphore(4) # at most 4 requests in flight
async def run(job):
async with limit:
return await call_model(job) # your async call with the retry loop aboveEjecuta bucles de agente en CheapTokens#
Para bucles de agente con muchas llamadas cortas, CheapTokens ofrece MiniMax-M2.7: un precio a cualquier hora del día, un contexto de 180K, hasta 8.192 tokens por respuesta y un solo saldo para todos los modelos. La salida cuesta $0.15 por 1M de tokens y, en total, contando entrada y salida juntas, es más barato que OpenRouter. Funciona a través del endpoint compatible con OpenAI https:/. El código anterior ya apunta a este endpoint; establece el id del modelo desde la documentación de CheapTokens y ejecútalo.
Precios en vivo para una carga de trabajo de 500.000 de entrada y 100.000 de salida:
| Modelo | Entrada $/1M | Salida $/1M | Costo | OpenRouter |
|---|---|---|---|---|
| GLM-5.3-Flash | $0.02 | $0.04 | $0.014 | $0.0282 |
| DeepSeek-V4-Flash | $0.015 | $0.10 | $0.0175 | $0.0394 |
| MiniMax-M2.7 | $0.05 | $0.15 | $0.04 | $0.27 |
500.000 tokens de entrada y 100.000 tokens de salida en total. Los precios por 1M de tokens son actuales y cambian junto con nuestras tarifas; la columna de OpenRouter muestra precios de referencia para la misma carga de trabajo.
Consulta todos los precios, la documentación de configuración, y crea una clave para enviar tu primera solicitud.
Si sigue fallando#
- 429 en la primera solicitud. Lee el tipo y el mensaje de error en el cuerpo de la respuesta: un 429 por gasto o límite de cuenta sigue fallando al reintentar hasta que se eleve el límite o se reanude el acceso, mientras que un 429 por límite de tasa se despeja tras una pausa.
- El 429 sigue llegando con un bucle largo. Puede que estés apilando reintentos: los reintentos del SDK multiplicados por tus reintentos. Pon el SDK a cero, como en los ejemplos.
- Los errores llegan en ráfagas al inicio de un lote. Aumenta gradualmente en lugar de lanzar todos los workers a la vez; OpenAI reporta
slow_downpara tráfico que crece demasiado rápido. - La llamada de streaming falló a mitad de camino. No la repitas a ciegas después de haber consumido salida; decide si reiniciar toda la respuesta o continuar desde lo que tienes.
- No hay Retry-After en la respuesta. El código recurre al backoff con jitter, así que no hace falta ningún cambio.



