DépannageGuide
429 Too Many Requests d'une API d'IA : nouvelles tentatives avec backoff
Ce que signifie un 429 d'une API d'IA et comment le gérer : backoff exponentiel avec jitter, Retry-After et concurrence des agents, avec du code Python et JavaScript.

Sur cette page
Un 429 Too Many Requests d'une API d'IA signifie que la requête a été rejetée parce que vous avez envoyé trop de requêtes trop vite, ou que vous avez atteint une limite sur votre compte. La solution consiste à attendre et à réessayer avec un backoff exponentiel et du jitter, à utiliser l'en-tête Retry-After lorsque la réponse en contient un, et à plafonner le nombre de requêtes que votre agent envoie en parallèle. Ne réessayez pas un 429 qui est en réalité une limite de dépenses ou de solde : attendre ne résoudra rien.
Ce guide s'appuie sur les guides officiels de limitation de débit de OpenAI et Anthropic, tous deux consultés le 22 octobre 2026. Aucune des deux pages n'affiche de date de dernière mise à jour.
Ce que signifie un 429#
OpenAI décrit un 429 comme une requête rejetée pour dépassement d'une limite de débit (rate_limit_error, avec le code slow_down lorsque le trafic augmente trop vite), ou parce qu'une limite de dépenses stricte a été atteinte. Anthropic utilise également rate_limit_error pour un 429. Un 429 dû à un plafond de dépenses n'a pas d'en-tête retry-after et continue d'échouer jusqu'à ce que l'accès soit rétabli.
Deux voisins sont faciles à confondre avec lui. Le 503 server_ d'OpenAI et le 529 overloaded_ d'Anthropic sont des conditions de surcharge, pas votre limite de débit. Ils sont généralement gérés avec la même boucle de nouvelle tentative, mais ils indiquent une cause différente.
Il y a donc trois cas, et chacun nécessite une réaction différente :
Faites défiler le tableau vers la droite →
| Réponse | Cause | Que faire |
|---|---|---|
| 429 limite de débit | Trop de requêtes ou de tokens par minute | Attendre, réessayer avec backoff |
| 429 limite de dépenses ou de solde | Limite du compte atteinte | Ne pas réessayer ; corriger la limite ou recharger le solde |
| 503 / 529 surchargé | Le côté fournisseur est occupé | Réessayer avec backoff, avec un plafond |
Si votre solde est épuisé, lisez plutôt l'erreur « not enough tokens ». Pour les échecs au niveau réseau tels que les timeouts et les connexions refusées, voir les erreurs de connexion courantes.
Lisez les en-têtes avant de deviner un délai#
OpenAI documente ces en-têtes sur les réponses limitées en débit : Retry-After, et x-, x-, x- avec les variantes -tokens. D'autres API peuvent envoyer un sous-ensemble ou aucun d'entre eux, donc écrivez le client pour utiliser Retry-After lorsqu'il est présent et revenir à votre propre planification lorsqu'il ne l'est pas.
Les recommandations d'OpenAI sur les nouvelles tentatives, en bref :
- Traitez
Retry-Aftercomme l'attente minimale et ajoutez un petit délai aléatoire par-dessus. - Sans cela, utilisez un backoff exponentiel avec jitter.
- Plafonnez à la fois le nombre de tentatives et le temps total de nouvelle tentative.
- N'empilez pas vos propres nouvelles tentatives sur celles du SDK.
- Ne réessayez pas les erreurs de quota ou de facturation.
- Les requêtes échouées comptent toujours dans la limite par minute, donc une boucle agressive aggrave les choses.
- Pour le streaming, ne rejouez pas une requête après avoir déjà consommé sa sortie.
Python : backoff avec jitter#
Le package officiel openai fonctionne avec tout endpoint compatible OpenAI. Désactivez les nouvelles tentatives propres au SDK (max_retries=0) pour qu'elles ne se multiplient pas avec les vôtres. L'id du modèle provient de la documentation CheapTokens ; les ids diffèrent selon les fournisseurs.
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)La borne supérieure du délai sans en-tête croît 1, 2, 4, 8 secondes (plafonnée à 30), et random.uniform choisit une valeur aléatoire en dessous, afin que de nombreux workers ne se réveillent pas tous au même moment. Retry-After peut aussi être une date HTTP ; le code ci-dessus gère la forme numérique et revient sinon au backoff.
JavaScript : la même boucle#
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);
}
}
}Quand cela fonctionne, un appel limité en débit prend simplement plus de temps puis renvoie une complétion normale. Quand il échoue encore après la dernière tentative, l'erreur d'origine est levée, afin que votre code puisse la journaliser ou mettre la tâche en file d'attente pour plus tard.
Comment les agents doivent gérer un 429#
Une boucle d'agent envoie de nombreux appels courts, et plusieurs agents ou appels d'outils peuvent s'exécuter en même temps. Réessayer seul ne suffit pas ; réduisez le débit de requêtes à la source :
- Plafonnez la concurrence. Un sémaphore de 3 à 5 appels parallèles est un point de départ raisonnable ; augmentez-le uniquement tant que les erreurs restent rares.
- Partagez un seul limiteur. Si plusieurs workers utilisent la même clé, ils partagent les mêmes limites, donc une boucle de nouvelle tentative par worker ne suffit pas : utilisez une seule file d'attente ou un seul sémaphore pour le processus.
- Reculez globalement. Quand un appel reçoit un 429 avec un délai, mettez en pause tout le pool pendant ce temps plutôt que seulement l'appel en échec.
- Gardez des requêtes plus petites. Moins de tokens par requête signifie moins de pression sur les limites de tokens par minute : réduisez l'historique et la sortie des outils. Voir comment estimer le coût d'un agent d'IA pour savoir où vont les tokens.
- Arrêtez-vous sur les erreurs non réessayables. Un 401, un 400 ou une erreur de solde ne se corrigera pas tout seul ; échouez rapidement et signalez-le.
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 aboveExécutez des boucles d'agent sur CheapTokens#
Pour les boucles d'agent avec de nombreux appels courts, CheapTokens propose MiniMax-M2.7 : un prix à toute heure de la journée, un contexte de 180K, jusqu'à 8 192 tokens par réponse, et un solde unique pour tous les modèles. La sortie coûte $0.15 par million de tokens, et au total, entrée et sortie comptées ensemble, c'est moins cher qu'OpenRouter. Cela fonctionne via l'endpoint compatible OpenAI https:/. Le code ci-dessus pointe déjà vers cet endpoint ; définissez l'id du modèle depuis la documentation CheapTokens et exécutez-le.
Prix en direct pour une charge de travail de 500 000 en entrée et 100 000 en sortie :
| Modèle | Entrée $/1M | Sortie $/1M | Coût | 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 d'entrée et 100 000 tokens de sortie au total. Les prix par million de tokens sont actuels et évoluent avec nos tarifs ; la colonne OpenRouter indique des prix de référence pour la même charge de travail.
Voir tous les prix, la documentation d'installation, et créez une clé pour envoyer votre première requête.
Si cela échoue encore#
- 429 dès la toute première requête. Lisez le type et le message d'erreur dans le corps de la réponse : un 429 de dépenses ou de limite de compte continue d'échouer à chaque nouvelle tentative jusqu'à ce que la limite soit relevée ou que l'accès soit rétabli, tandis qu'un 429 de limite de débit disparaît après une pause.
- Le 429 revient sans cesse avec une longue boucle. Vous empilez peut-être les nouvelles tentatives : celles du SDK multipliées par les vôtres. Réglez le SDK à zéro, comme dans les exemples.
- Les erreurs arrivent en rafale au début d'un lot. Montez en charge progressivement au lieu de lancer tous les workers d'un coup ; OpenAI signale
slow_downpour un trafic qui croît trop vite. - Un appel en streaming a échoué en cours de route. Ne le rejouez pas aveuglément après avoir consommé la sortie ; décidez s'il faut redémarrer toute la réponse ou continuer à partir de ce que vous avez.
- Pas de Retry-After dans la réponse. Le code revient au backoff avec jitter, donc aucun changement n'est nécessaire.



