Erreurs et limites
Une seule enveloppe d'erreur, le tableau complet des codes, la limite de débit, et la règle simple pour savoir ce qui mérite un réessai.
Une seule enveloppe
Toute erreur rendue par une route sous /v1 prend la même forme, celle des erreurs d'OpenAI : un objet error au premier niveau. C'est vrai quelle que soit l'origine du refus, y compris sur une requête mal formée, un corps illisible, un dépassement de débit ou une panne interne.
{
"error": {
"message": "...",
"type": "...",
"code": "...",
"param": null
}
}
| Champ | Ce qu'il porte |
|---|---|
message | Une phrase qui décrit la cause en termes métier. Jamais le message brut d'un fournisseur, jamais la valeur que vous avez envoyée. |
type | La famille d'erreur, dans le vocabulaire d'OpenAI : invalid_request_error, authentication_error, permission_error, rate_limit_error, insufficient_quota, api_error… |
code | Le code machine, stable, à tester dans votre code. Il peut valoir null sur les statuts qui n'en ont pas : repliez alors sur le statut HTTP. |
param | Le champ fautif sur une erreur de validation (par exemple temperature), null sinon. |
Sur les routes au format Anthropic (/v1/messages et /v1/messages/count_tokens), la même enveloppe porte en plus un champ type valant error au premier niveau : c'est la forme attendue par un SDK Anthropic, et les deux formats partagent déjà error.type et error.message.
{
"type": "error",
"error": {
"message": "...",
"type": "authentication_error",
"code": "invalid_api_key",
"param": null
}
}
Le refus du filtre des données sensibles (451) utilise la même enveloppe, enrichie d'un bloc netaix décrit sur sa page. Et cette enveloppe vaut pour /v1 : les routes de l'application sous /api, qui ne vous concernent pas ici, gardent leur propre forme.
Une erreur survenue après le début d'un flux ne peut plus changer le statut HTTP, déjà parti en 200. Elle arrive dans le flux lui-même : un fragment data: {"error": {...}} au format OpenAI, un événement error au format Anthropic. Un lecteur qui ne regarde que le statut affichera une réponse tronquée comme une réponse complète.
Tous les codes
| HTTP | Code | Cause | Réessayer ? |
|---|---|---|---|
400 | dimensions_not_supported, unsupported_encoding_format, input_must_be_text, too_many_inputs, input_too_long, request_too_large | Requête mal formée, ou borne de volume dépassée sur /embeddings. | Non. Corrigez la requête. |
400 | invalid_request | Requête invalide : champ hors bornes (param le nomme), corps qui n'est pas du JSON, ou aucune clé de fournisseur configurée pour le modèle demandé. | Non. Corrigez la requête. |
400 | usage_not_available_for_this_credential | Sur /netaix/usage : l'identifiant utilisé n'est pas une clé d'API et n'a donc pas de relevé propre. | Non. |
401 | invalid_api_key | La clé n'a pas été acceptée. Une seule réponse pour toutes les causes : voir la note sous ce tableau. | Non. Vérifiez la clé. |
402 | topup_required | Compte Développeur dont la première recharge n'est pas payée. | Non. Créditez le compte. |
402 | insufficient_balance | Compte Développeur dont le solde prépayé est épuisé. | Non. Rechargez. |
403 | model_not_allowed | Le modèle n'est pas ouvert à cette organisation, à ce compte, ou à cette clé. | Non. Voir GET /v1/models. |
404 | model_not_found | Sur /embeddings : le modèle demandé n'est pas un modèle d'embeddings disponible. | Non. |
404 | not_found | L'adresse appelée n'existe pas. Presque toujours un /v1 en trop ou en moins. | Non. Corrigez l'adresse. |
422 | invalid_image, unsupported_image_format, image_too_large, too_many_images, invalid_input, invalid_role, invalid_system | Une image, un rôle de message ou une consigne système ne respecte pas la forme attendue. | Non. Corrigez la requête. |
429 | rate_limit_exceeded | Débit trop élevé. Un en-tête Retry-After accompagne la réponse. | Oui, après l'attente indiquée. |
429 | too_many_auth_failures | Échecs d'authentification répétés depuis la même origine. Un en-tête Retry-After accompagne la réponse. | Oui, après l'attente indiquée, et vérifiez la clé. |
429 | key_daily_quota_exceeded | Plafond du jour de la clé atteint. | Non avant minuit UTC, ou relevez le plafond. |
429 | key_quota_exceeded | Plafond de cycle de la clé atteint. | Non. Relevez le plafond, ou utilisez une autre clé. |
429 | quota_exceeded | Enveloppe de l'organisation épuisée. | Non. Rechargez. |
451 | netaix_dlp_blocked | Le filtre des données sensibles a arrêté l'envoi. | Non, tant que l'administrateur n'a pas autorisé. Voir la page dédiée. |
502 | upstream_error | Le fournisseur du modèle a échoué, ou la réponse est inexploitable. En flux, le même échec arrive dans un fragment d'erreur portant le code inference_error. | Oui, avec attente croissante. |
503 | moderation_unavailable | Aucun verdict de modération n'a pu être produit. Ce n'est pas un verdict favorable. | Oui, après Retry-After. |
503 | embeddings_unavailable | Le service d'embeddings a échoué. | Oui, après Retry-After s'il est présent, sinon avec attente croissante. |
503 | sans code | Indisponibilité ponctuelle de la passerelle. | Oui, avec attente croissante. |
Les messages d'erreur rendus par la passerelle décrivent la cause en termes métier. Ils ne contiennent jamais le message brut d'un fournisseur ni de détail d'infrastructure : si vous avez besoin de plus pour un incident, donnez-nous l'heure de l'appel et le début de la clé concernée.
Pourquoi un 401 ne dit pas pourquoi
Tous les refus de clé rendent exactement la même réponse : le même statut 401, le même message, le même code invalid_api_key. Clé absente, clé inconnue, clé révoquée, clé expirée, clé mal formée, clé qui n'a pas les droits nécessaires : rien ne les distingue vu de l'extérieur.
C'est voulu. Un refus qui dit « expirée » ou « révoquée » confirme à qui essaie des clés au hasard que celle-ci a existé, et renseigne donc son prochain essai. Le motif réel est enregistré de notre côté : si vous êtes bloqué, écrivez-nous avec l'heure de l'appel et le début de la clé, nous le lisons.
Côté client, cela ne change rien à ce qu'il faut faire : un 401 ne se réessaie pas, il se corrige. Vérifiez que la clé est bien celle que vous croyez, qu'elle n'a pas été révoquée dans l'application, et qu'elle est envoyée dans l'un des deux en-têtes acceptés (voir Authentification).
Limite de débit
Chaque route de l'API est limitée à 60 requêtes par minute, sauf GET /v1/netaix/usage, limitée à 30. Au-delà, la passerelle rend un 429 de code rate_limit_exceeded, dans l'enveloppe habituelle.
Ce 429 porte un en-tête Retry-After, en secondes, calculé sur la fenêtre réellement en cours. Respectez-le : il vaut mieux que votre estimation. S'il manque, repliez sur une attente croissante (1 s, 2 s, 4 s, 8 s) avec une part aléatoire.
Pour une application en production qui a besoin de plus, écrivez-nous : la limite se relève, et elle se discute sur des chiffres (débit visé, régularité, heures de pointe).
La stratégie de réessai que nous recommandons
Elle tient en une règle : ne réessayez que ce qui peut changer tout seul.
- Jamais de réessai sur
400,401,402,403,404,422,451, et sur les429dont letypevautinsufficient_quota. Ces refus sont définitifs tant que quelque chose n'a pas été changé par une personne. - Réessai avec attente croissante sur
429de débit,502et503: par exemple 1 s, 2 s, 4 s, 8 s, cinq tentatives au plus, avec une part aléatoire ajoutée à chaque attente pour que plusieurs de vos processus ne repartent pas en même temps. - Respectez
Retry-Afterquand l'en-tête est présent : il vaut mieux que votre calcul. - Posez un délai maximal sur vos appels, et traitez l'expiration comme un échec à réessayer. Un appel en flux peut rester ouvert longtemps : mesurez le délai avant le premier fragment, pas la durée totale.
Quand un fournisseur tombe
Pour les comptes chez qui elle est activée, la passerelle peut rejouer une fois l'appel sur un autre modèle de niveau équivalent quand le fournisseur sollicité est en panne. Ce que cela couvre et ce que cela ne couvre pas, parce que la frontière compte :
| Situation | Rejoué ailleurs ? |
|---|---|
Délai dépassé, connexion refusée, erreur 5xx ou 429 du fournisseur | Oui, une fois. |
| Le modèle refuse le contenu de votre requête | Non. Chercher un fournisseur plus complaisant n'est pas un repli. |
| Requête invalide, paramètre ou document refusé par le fournisseur | Non : la seconde tentative échouerait de la même façon. |
| Échec survenu après le premier fragment de réponse | Non : la réponse a commencé, elle ne peut plus être remplacée. |
- Seuls les niveaux
netaix-*en bénéficient. Un modèle demandé par son nom n'est jamais remplacé : vous avez nommé ce modèle, nous ne lui en substituons pas un autre. - Le remplaçant reste au même niveau et dans les droits de votre clé. Quand votre organisation a fixé une liste de modèles, le repli ne sort pas de cette liste.
- Une seule facturation, au nom du modèle qui a réellement répondu. C'est celui que porte le champ
modelde la réponse : lisez-le si vous voulez savoir ce qui vous a servi. - Ce n'est pas actif par défaut. Écrivez-nous pour l'ouvrir sur votre organisation.
Sans cette bascule, et quand aucun remplaçant n'est disponible, l'échec du fournisseur vous est renvoyé tel quel (502) et c'est votre application qui décide de réessayer. Prévoyez le cas : une tentative sur netaix-auto, ou sur un modèle d'une autre maison. Nous préférons l'écrire plutôt que de laisser croire à une continuité qui n'est pas promise.
Un mot sur les doublons
Il n'y a pas de clé d'idempotence sur l'API : un appel réessayé après un délai dépassé peut avoir été exécuté la première fois, et vous serez alors facturé deux fois pour un seul résultat utile. Si cela compte pour votre usage, gardez de votre côté la trace de ce qui a abouti, et préférez un délai large à un réessai hâtif.