Limites OpenRouter et erreur HTTP 429 : diagnostic et résolution
Un guide complet pour diagnostiquer les erreurs HTTP 429 et 402 sur OpenRouter : distinguer le quota du forfait gratuit des limites de la plateforme et des fournisseurs amont, inspecter les en-têtes et métadonnées, et intégrer un script de nouvelle tentative résilient en Python.
Sommaire

Lors de l’envoi d’un volume important de requêtes vers des modèles d’intelligence artificielle via la passerelle OpenRouter, les applications clientes rencontrent régulièrement le code de statut HTTP 429 Too Many Requests. Étant donné qu’OpenRouter regroupe des dizaines de fournisseurs d’inférence indépendants, cette erreur peut se produire à différents niveaux de l’infrastructure. Une nouvelle tentative immédiate et naïve entraîne souvent le blocage du client ou le gaspillage des quotas d’appels. Pour rétablir un flux d’envoi stable, il est nécessaire d’identifier la couche exacte de défaillance et d’adopter la stratégie correspondante : marquer une pause d’attente (backoff), réduire la concurrence, basculer de fournisseur ou ajuster les plafonds budgétaires.
Niveaux de limites : forfait gratuit, plateforme et fournisseur amont
La documentation officielle OpenRouter Limits distingue clairement les limitations de fréquence du service, le débit de traitement des fournisseurs amont et les blocages liés au solde du compte.
Sur la page des tarifs OpenRouter Pricing, le forfait gratuit de base fixe un plafond de 50 requêtes par jour pour les modèles gratuits accessibles publiquement (portant le suffixe :free). Les limites exactes en requêtes par minute (RPM) et les seuils de paliers pouvant évoluer au fil du temps, les valeurs chiffrées réelles doivent toujours être vérifiées sur le tableau des limites en direct.
Lors de l’analyse d’une interruption, il est capital de différencier les paliers responsables de l’émission du code 429 et du statut connexe 402 :
- Limites de fréquence de la plateforme OpenRouter. Elles surviennent lorsque le routeur lui-même est sollicité à une fréquence excessive. Le quota quotidien du groupe gratuit relève également de cette catégorie de restrictions de la plateforme (et non d’une restriction distincte d’un tiers) : en cas de dépassement de la limite de 50 requêtes par jour sur les modèles gratuits, la plateforme rejette les requêtes jusqu’à la réinitialisation du compteur quotidien. Lorsqu’une limite de fréquence au niveau de la plateforme est atteinte, le serveur renvoie les en-têtes HTTP
X-RateLimit-Limit,X-RateLimit-RemainingetX-RateLimit-Reset. Les réponses réussies accompagnées du codeHTTP 200ne contiennent pas ces en-têtes opérationnels, ce qui empêche les applications clientes de s’y fier en régime de fonctionnement normal pour anticiper les limites. - Limites de fréquence du fournisseur amont (upstream rate limit). Les modèles sont physiquement hébergés et exécutés sur les serveurs d’entreprises partenaires spécifiques (telles qu’Anthropic, Meta, DeepSeek, Mistral ou des hébergeurs cloud spécialisés). Si l’infrastructure de ce partenaire est saturée, OpenRouter répercute le statut 429 vers le client. Dans la structure du corps de réponse, le champ
error.metadata.provider_codecontient le code d’erreur brut initial du fournisseur amont lorsqu’il est disponible (par exemple 429), et non le nom textuel ou l’identifiant du fournisseur. - Contraintes financières (
HTTP 402 Payment Required). La documentation OpenRouter Limits sépare explicitement l’épuisement des crédits des restrictions de cadence. Le statut 402 signale un solde insuffisant ou négatif sur le compte de l’organisation, ou l’atteinte du plafond individuel attribué à une clé d’API (key cap), plutôt que de désigner exclusivement un compte strictement à zéro.
Les paramètres actuels d’une clé peuvent être contrôlés par un appel direct à l’API :
curl -s -X GET https://openrouter.ai/api/v1/key \
-H "Authorization: Bearer $OPENROUTER_API_KEY"
Le corps de réponse renvoie les champs usage, limit_reset et limit_remaining. Une valeur limit_remaining: null indique qu’aucun plafond de dépense local (key cap) n’est appliqué à cette clé API spécifique. Cette valeur ne confirme nullement la présence de crédits sur le solde principal de l’organisation ; elle signale simplement l’absence de restriction artificielle sur ce jeton particulier.
Tableau récapitulatif de diagnostic
| Code de réponse et signaux | Source d’inspection | Cause du problème | Action du client |
|---|---|---|---|
HTTP 402 Payment Required | Point de terminaison GET /api/v1/key ou tableau de bord | Solde de l’organisation insuffisant ou négatif, ou plafond de la clé atteint (limit_remaining: 0) | Recharger le compte ou augmenter le quota du jeton ; une nouvelle tentative programmée sans modification est inutile |
HTTP 429 avec en-têtes X-RateLimit-* | En-têtes HTTP de réponse de la passerelle | Dépassement des limites de concurrence ou de fréquence de la plateforme OpenRouter elle-même | Lire l’en-tête Retry-After et réduire le nombre de threads simultanés |
HTTP 429 avec code dans provider_code | Champ JSON error.metadata.provider_code (optionnel) | Surcharge ou défaillance du fournisseur amont (code d’erreur brut de l’upstream) | Changer de modèle ou de fournisseur peut aider mais ne garantit pas la reprise ; vérifier le fournisseur concerné dans Activity > provider_responses |
HTTP 429 sur modèles :free | Section OpenRouter Pricing | Quota quotidien de la plateforme épuisé (50 requêtes/jour) ou capacité globale atteinte | Passer à un modèle payant ou différer l’exécution de la tâche |
HTTP 429 lors d’un routage complexe | Tableau de bord : Activity > Requête > View Raw Metadata | Échec d’un nœud intermédiaire dans l’objet provider_responses | Identifier le fournisseur défaillant et vérifier la chaîne de repli dans le guide BYOK/Routing |
Analyse de deux scénarios : plafond de clé vs panne du fournisseur
La façon dont une application cliente réagit lors d’une interruption de requêtes repose sur l’inspection des métadonnées. Voici deux scénarios hypothétiques (à titre d’exemples illustratifs et non d’observations d’un compte réel).
Scénario 1 (hypothétique) : Épuisement du plafond local du jeton
Dans ce scénario hypothétique, un processus d’arrière-plan reçoit un refus avec le code HTTP 402. Un solde d’organisation positif constitue ici une hypothèse de départ explicite, vérifiée séparément sur le tableau de bord web (la réponse du point de terminaison de clé ne confirme pas à elle seule l’état du compte global). Une requête adressée au point de terminaison https://openrouter.ai/api/v1/key renvoie :
{
"data": {
"label": "worker-key",
"usage": 25.04,
"limit": 25.0,
"is_free_tier": false,
"limit_remaining": 0.0,
"limit_reset": null
}
}
Bien que le solde du compte principal soit positif selon les prémisses, le champ limit_remaining est égal à zéro. Le jeton a atteint le plafond de dépense de 25 dollars défini par l’administrateur. Toute nouvelle tentative avec cette clé se soldera par la même erreur 402. Le processus doit s’interrompre immédiatement et alerter un administrateur afin d’ajuster le plafond de la clé.
Scénario 2 (hypothétique) : Surcharge du fournisseur amont
À titre d’illustration, considérons le cas où une requête renvoie HTTP 429 en raison d’une panne du côté de l’infrastructure amont. Le simple fait de recevoir un code 429 ne permet en aucun cas de conclure sur l’état général de la passerelle ou sur la présence d’un solde positif. Le corps d’erreur peut contenir un bloc de métadonnées optionnel :
{
"error": {
"message": "Provider returned rate limit error",
"code": 429,
"metadata": {
"provider_code": 429
}
}
}
Le champ error.metadata.provider_code est optionnel et indique le code d’erreur d’origine du fournisseur amont lorsqu’il est disponible (dans cet exemple, 429), et non le nom ou l’identifiant du fournisseur. La présence de ce seul code ne permet pas d’identifier quel hébergeur spécifique a rejeté l’appel.
Pour déterminer avec certitude le fournisseur défaillant, il est nécessaire d’ouvrir dans la console de gestion la section : Activity > requête concernée > View Raw Metadata. L’objet provider_responses détaille la liste des hôtes interrogés ainsi que leurs statuts réels, comme décrit dans le guide de routage. Basculer vers un autre fournisseur ou modifier le modèle cible peut aider dans une telle situation, mais ne garantit pas un rétablissement immédiat.
Script client de nouvelle tentative en Python 3
Lorsque le statut HTTP 429 présente un caractère temporaire, le délai avant la tentative suivante est calculé à partir de l’en-tête Retry-After. Cet en-tête est transmis par le serveur sous la forme d’un nombre entier de secondes ou d’une date au format HTTP.
Le script ci-dessous s’appuie exclusivement sur la bibliothèque standard de Python 3. Il traite uniquement le code 429, applique un intervalle exponentiel avec aléa (jitter) en l’absence d’indication du serveur, et stoppe l’exécution si le serveur exige une attente supérieure à 60 secondes.
import email.utils
import json
import os
import random
import sys
import time
import urllib.error
import urllib.request
API_KEY = os.environ.get("OPENROUTER_API_KEY")
MODEL_ID = os.environ.get("OPENROUTER_MODEL_ID", "openai/gpt-4o-mini")
MAX_ATTEMPTS = 3
MAX_ACCEPTABLE_WAIT = 60.0
def parse_retry_after(header_value: str | None) -> float | None:
if not header_value:
return None
raw = header_value.strip()
if raw.isdigit():
return max(0.0, float(raw))
try:
parsed_date = email.utils.parsedate_to_datetime(raw)
delay = parsed_date.timestamp() - time.time()
return max(0.0, delay)
except Exception:
return None
def execute_completion(prompt_text: str) -> str | None:
if not API_KEY:
sys.stderr.write("Переменная окружения OPENROUTER_API_KEY не задана.\n")
return None
endpoint = "https://openrouter.ai/api/v1/chat/completions"
payload = json.dumps(
{
"model": MODEL_ID,
"messages": [{"role": "user", "content": prompt_text}],
}
).encode("utf-8")
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
for attempt in range(1, MAX_ATTEMPTS + 1):
req = urllib.request.Request(endpoint, data=payload, headers=headers, method="POST")
try:
with urllib.request.urlopen(req, timeout=30) as response:
status_code = response.getcode()
body = response.read().decode("utf-8")
if status_code == 200:
data = json.loads(body)
return data["choices"][0]["message"]["content"]
except urllib.error.HTTPError as err:
if err.code == 429:
retry_header = err.headers.get("Retry-After")
server_delay = parse_retry_after(retry_header)
if server_delay is not None:
wait_seconds = server_delay
else:
base_delay = 2.0 ** attempt
wait_seconds = base_delay + random.uniform(0.1, 1.0)
if wait_seconds > MAX_ACCEPTABLE_WAIT:
sys.stderr.write(
f"Сервер запросил паузу {wait_seconds:.1f} с. "
"Ожидание превышает 60 секунд. Запрос отменен.\n"
)
return None
if attempt == MAX_ATTEMPTS:
sys.stderr.write("Исчерпан лимит из 3 попыток на статус 429.\n")
return None
sys.stderr.write(
f"Получен 429. Попытка {attempt} завершилась неудачей. "
f"Пауза {wait_seconds:.2f} с перед следующим запросом.\n"
)
time.sleep(wait_seconds)
continue
elif err.code == 402:
sys.stderr.write("Ошибка 402: проверьте баланс счета и лимит ключа.\n")
return None
else:
sys.stderr.write(f"HTTP-ошибка {err.code}: запрос отклонен без повтора.\n")
return None
except urllib.error.URLError as err:
sys.stderr.write(f"Сетевой сбой: {err.reason}. Повтор отменен.\n")
return None
return None
if __name__ == "__main__":
result = execute_completion("Назови три базовых принципа надежности сетевых API.")
if result:
print(result)
Comme le fragment de code ci-dessus conserve à l’octet près ses chaînes de diagnostic originales en russe, voici l’explication de son arbre de décision interne et leurs équivalents traduits :
Переменная окружения OPENROUTER_API_KEY не задана(La variable d’environnement OPENROUTER_API_KEY n’est pas définie) : signale l’absence de clé d’API ; la fonction s’arrête immédiatement sans émettre d’appel réseau.Сервер запросил паузу ... Ожидание превышает 60 секунд. Запрос отменен(Le serveur a demandé une pause de … L’attente dépasse 60 secondes. Requête annulée) : déclenché lorsque le délai imposé par l’en-têteRetry-AfterdépasseMAX_ACCEPTABLE_WAIT(60 secondes) ; l’exécution est annulée pour ne pas bloquer les processus indéfiniment.Исчерпан лимит из 3 попыток на статус 429(Limite de 3 tentatives épuisée pour le statut 429) : indique que les 3 tentatives autorisées ont toutes échoué face à des réponses HTTP 429.Получен 429. Попытка X завершилась неудачей. Пауза Y с перед следующим запросом(429 reçu. La tentative X a échoué. Pause de Y s avant la requête suivante) : journalise un échec intermédiaire dû à la limite de fréquence lors de la tentative X et attend la durée calculée Y avant de réessayer.Ошибка 402: проверьте баланс счета и лимит ключа(Erreur 402 : vérifiez le solde du compte et le plafond de la clé) : signale une erreur de paiement non récupérable par une simple relance ; aucune nouvelle tentative n’est effectuée.HTTP-ошибка {code}: запрос отклонен без повтора(Erreur HTTP {code} : requête rejetée sans nouvelle tentative) : consigne tout autre code de statut HTTP et met fin à l’exécution.Сетевой сбой: {reason}. Повтор отменен(Défaillance réseau : {reason}. Nouvelle tentative annulée) : intercepte les pannes réseau ou de socket (URLError) et interrompt le processus afin d’éviter de réémettre une requête dont l’état de traitement initial est incertain.- La consigne de test
Назови три базовых принципа надежности сетевых APIse traduit par : « Nomme trois principes fondamentaux de fiabilité pour les API réseau. »
Nouvelles tentatives de requêtes réseau et effets secondaires
Lors de la conception de flux avec appel d’outils (tool calling) au sein de boucles d’agents autonomes, la répétition d’une requête HTTP impose une vigilance extrême. Si le modèle a déjà déclenché lors d’une étape précédente l’exécution d’un outil externe ayant modifié l’état d’un système distant (écriture dans une base de données, exécution d’un paiement ou création d’un ticket de support), relancer l’ensemble de la séquence conduira à des opérations dupliquées. En aucun cas une requête ne doit être répétée automatiquement si des outils métier associés ont déjà été exécutés, ou face à un résultat réseau incertain (tel qu’une interruption de connexion ou un dépassement de délai d’attente réseau, où l’on ignore si la requête a été reçue et traitée par le serveur).
Dans l’implémentation proposée, la nouvelle tentative est strictement limitée aux requêtes explicitement rejetées par le serveur avec le statut HTTP 429. De plus, la génération de texte ne saurait être considérée comme strictement idempotente ou gratuite : relancer des requêtes consomme du budget et des quotas de jetons, et la nature stochastique des modèles peut produire des sorties divergentes. Si une défaillance survient alors qu’un agent exécute une commande externe, l’état du système doit être réconcilié via un journal d’actions avant de reprendre l’échange avec le modèle linguistique.
Conception d’une architecture de routage résiliente
Sur la page des tarifs, le forfait Free applique un plafond au niveau de la plateforme fixé à 50 requêtes par jour sur les modèles gratuits. Il convient de noter que l’absence de limites de fréquence de la plateforme mentionnée dans la documentation correspond au passage à un modèle payant, et non au simple approvisionnement du compte : créditer le solde ne lève pas automatiquement les restrictions journalières sur les points de terminaison gratuits :free. Les conditions précises et les quotas appliqués à votre compte doivent toujours être vérifiés sur le tableau des limites en direct. En outre, même l’usage de modèles payants n’élimine pas les surcharges temporaires sur les grappes de serveurs de certains fournisseurs (et bien que le changement de fournisseur puisse aider, il ne garantit pas une reprise instantanée).
Afin d’assurer la haute disponibilité de leurs services en production, les équipes d’ingénierie combinent plusieurs stratégies défensives :
- Spécifier des modèles de secours dans le tableau
modelsde la requête OpenRouter, ce qui permet à la passerelle de rediriger automatiquement le trafic vers un autre fournisseur en cas de défaillance du modèle principal. - Restreindre le nombre maximal de requêtes simultanées côté client à l’aide de files d’attente de tâches ou de limiteurs de débit.
- Maintenir pour les composants d’infrastructure critiques une route de secours indépendante via d’autres API multimodèles proposant un format de requête compatible, garantissant ainsi une continuité de service lors des pannes prolongées de la passerelle principale.