Invitez et gagnez

Fonctionnement des récompenses

Partagez votre lien. Lorsqu’un ami s’inscrit avec ce lien et recharge son solde, vous recevez la récompense affichée sur ses recharges ultérieures.

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
Limites OpenRouter et erreur HTTP 429 : diagnostic et résolution

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 :

  1. 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-Remaining et X-RateLimit-Reset. Les réponses réussies accompagnées du code HTTP 200 ne 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.
  2. 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_code contient 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.
  3. 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 signauxSource d’inspectionCause du problèmeAction du client
HTTP 402 Payment RequiredPoint de terminaison GET /api/v1/key ou tableau de bordSolde 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 passerelleDépassement des limites de concurrence ou de fréquence de la plateforme OpenRouter elle-mêmeLire l’en-tête Retry-After et réduire le nombre de threads simultanés
HTTP 429 avec code dans provider_codeChamp 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 :freeSection OpenRouter PricingQuota quotidien de la plateforme épuisé (50 requêtes/jour) ou capacité globale atteintePasser à un modèle payant ou différer l’exécution de la tâche
HTTP 429 lors d’un routage complexeTableau de bord : Activity > Requête > View Raw MetadataÉchec d’un nœud intermédiaire dans l’objet provider_responsesIdentifier 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ête Retry-After dépasse MAX_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 Назови три базовых принципа надежности сетевых API se 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 models de 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.

Prêt à optimiser votre workflow LLM ?

Connectez vos modèles via une API unique, gérez les clés et maîtrisez vos dépenses d’IA.

Commencer gratuitement