Erreurs
Chaque échec est un corps JSON accompagné d’un statut HTTP. Voici la réponse à une requête envoyée sans clé :
{
"error": {
"message": "Missing API key. Send it as: Authorization: Bearer <your OceanToken key>.",
"type": "authentication_error",
"param": null,
"code": "invalid_api_key"
}
}
Basez vos branchements sur code. C’est un vocabulaire fixe que nous gardons stable ; message est destiné aux personnes et sa formulation peut changer. type regroupe les codes en familles qui se traitent de la même façon. Le statut HTTP dit la même chose avec moins de précision : les clients existants qui s’appuient dessus continuent de fonctionner.
Codes
| HTTP | type | code | Ce qui s’est passé | Que faire |
|---|---|---|---|---|
| 400 | invalid_request_error | invalid_request | La requête était mal formée : un champ manquant, une valeur que le modèle n’accepte pas, une image invalide. | Corrigez la requête. Ne la renvoyez pas telle quelle. |
| 400 | not_found_error | model_not_found | Le nom de model n’existe pas. | Choisissez un nom dans GET /v1/models. |
| 400 | insufficient_credits | insufficient_credits | Vos crédits disponibles sont épuisés. Rien n’a été exécuté ni facturé. | Ajoutez des crédits, puis réessayez. |
| 400 | insufficient_credits | key_budget_exceeded | La limite de crédits propre à cette clé est atteinte (votre compte a peut-être encore des crédits). | Relevez la limite de la clé, attendez sa réinitialisation ou utilisez une autre clé. |
| 400 | insufficient_credits | request_exceeds_balance | Votre solde est supérieur à 0 $, mais ce qui reste une fois réservée la part de vos requêtes en cours est inférieur au coût maximal possible de cette requête. Ce pire cas correspond à l’entrée au prix d’entrée plein, plus max_tokens de sortie (plafonné à la sortie maximale du modèle ; sans max_tokens, cette limite, au plus 16 384 tokens), multiplié par n, plus tout frais par requête ou de recherche, au prix le plus élevé que pourrait facturer n’importe quel canal du modèle, prix de contexte long et de raisonnement compris. Rien n’a été exécuté ni facturé. | Baissez max_tokens ou raccourcissez l’entrée, ou ajoutez des crédits. |
| 400 | content_policy_violation | content_filtered | Un contrôle de sécurité a refusé la requête avant l’exécution du modèle. Non facturé. | Modifiez l’entrée. Réessayer avec la même entrée donne la même réponse. |
| 401 | authentication_error | invalid_api_key | Pas de clé, une clé inconnue ou une clé supprimée. | Vérifiez l’en-tête Authorization: Bearer sk-.... |
| 401 | authentication_error | key_expired | La date d’expiration de la clé est passée. | Créez une nouvelle clé. |
| 403 | permission_error | model_not_allowed | La clé est valide, mais ce modèle ne fait pas partie de ceux autorisés pour votre compte. | Utilisez un modèle de GET /v1/models. |
| 403 | permission_error | forbidden | La clé ne peut pas effectuer cette opération. La gestion des clés API en fait partie : une clé API ne peut ni créer, ni modifier, ni faire tourner, ni supprimer des clés. | Gérez les clés dans la console ; sinon, utilisez une clé ayant la bonne portée. |
| 404 | not_found_error | not_found | Chemin inconnu, ou un id de vidéo / de tâche qui n’existe pas. | Vérifiez l’URL et l’id. |
| 409 | idempotency_error | idempotency_key_reused | L’Idempotency-Key a déjà été utilisée avec un corps différent. | Utilisez une nouvelle clé pour une nouvelle requête. Voir Nouvelles tentatives. |
| 409 | idempotency_error | idempotency_key_in_progress | La première requête avec cette clé est toujours en cours. | Attendez Retry-After secondes, puis renvoyez la même requête. |
| 429 | rate_limit_error | rate_limited | Vous avez dépassé la limite de requêtes ou de tokens par minute de votre clé. | Patientez jusqu’à Retry-After ; voir Limites. |
| 429 | rate_limit_error | provider_overloaded | Tous les canaux du modèle sont saturés en ce moment. Voir Nouvelles tentatives et basculement. | Réessayez avec un délai croissant, ou changez de modèle. |
| 501 | invalid_request_error | not_supported | Cet endpoint n’est pas proposé : lots (/v1/batches), fine-tuning (/v1/fine_tuning), et lister, lire ou supprimer des fichiers sous /v1/files. | Ne réessayez pas. Envoyez plutôt chaque requête à son endpoint habituel. |
| 503 | api_error | temporarily_unavailable | La passerelle n’a pas pu vérifier à l’instant ce dont elle a besoin pour accepter la requête, comme le solde de votre organisation. Rien n’a été exécuté ni facturé. | Réessayez après Retry-After secondes. |
| 5xx | api_error | provider_error | Le modèle a échoué, après les nouvelles tentatives et le basculement de la passerelle. Non facturé. | Réessayez avec un délai croissant ; si cela persiste, changez de modèle. |
| 408 | api_error | timeout | Un stream n’a rien reçu du modèle pendant 120 secondes après son démarrage. | Réessayez avec un délai croissant. |
| 504 | api_error | timeout | La requête a dépassé la fenêtre de 240 secondes. | Passez la réponse en streaming, raccourcissez la requête ou utilisez un endpoint asynchrone. |
Tout ce qui ne figure pas dans ce tableau est une api_error. De nouveaux codes peuvent être ajoutés ; les codes existants ne sont ni renommés ni supprimés.
L’ID de requête
Les réponses aux requêtes de modèle portent l’en-tête x-oceantoken-request-id. Enregistrez-le avec vos erreurs et citez-le quand vous nous écrivez. Une requête refusée à cause de sa clé (401) n’a jamais atteint de modèle et n’en a pas.
Une réponse réussie a aussi un id dans son corps, et chaque fragment d’un stream porte le même id. C’est le Request ID que la console affiche sous Logs, avec le statut, la latence et le coût de la requête, et l’id que recherche GET /v1/usage/{request_id}.
Nouvelles tentatives
Avant qu’une erreur ne vous parvienne, la passerelle a déjà réessayé et, si le modèle a un autre canal, y a déplacé la requête. Nouvelles tentatives et basculement explique ce qu’elle gère et ce qu’elle vous laisse.
Réessayez uniquement dans ces cas : 429 ; tout 5xx sauf 501 ; une erreur timeout ; une panne réseau (connexion coupée, délai dépassé côté client). Tout ce qui est dans la plage 400, et 501 not_supported, échouera de la même façon tant que la requête ou le compte n’auront pas changé.
Utilisez un délai exponentiel avec une part aléatoire, en commençant vers une seconde et en plafonnant vers trente ; quand la réponse contient Retry-After (un nombre de secondes), attendez au moins ce temps-là.
Retry-After n’est garanti que sur 409 idempotency_key_in_progress ; sur un 429 ou un 503, il peut manquer : utilisez alors votre propre délai.
# Retry only what can succeed on a second try: 429, 408 (timeout), every 5xx
# except 501 (an endpoint that is not offered) and network failures,
# with exponential backoff and jitter, honouring Retry-After when it is sent.
import os
import random
import time
from openai import APIConnectionError, APIStatusError, OpenAI
client = OpenAI(
base_url=os.getenv("OCEANTOKEN_BASE_URL", "https://api.oceantoken.ai/v1"),
api_key=os.environ["OCEANTOKEN_API_KEY"],
max_retries=0, # the loop below does the retrying
)
def with_retries(call, attempts=5):
for attempt in range(attempts):
try:
return call()
except APIConnectionError:
if attempt == attempts - 1:
raise
delay = None # network failure: back off and try again
except APIStatusError as e:
retryable = e.status_code in (408, 429) or (e.status_code >= 500 and e.status_code != 501)
if not retryable or attempt == attempts - 1:
raise
retry_after = e.response.headers.get("retry-after")
delay = float(retry_after) if retry_after else None
time.sleep(delay if delay is not None else min(30, 2**attempt) + random.random())
models = with_retries(lambda: client.models.list())
print(len(models.data), "models")
Réessayer une requête payante
Les requêtes d’images, de vidéo et de synthèse vocale sont facturées à l’exécution. Une nouvelle tentative après un délai réseau dépassé les exécuterait sinon une seconde fois. Envoyez un en-tête Idempotency-Key (n’importe quelle chaîne unique, jusqu’à 255 caractères, par exemple un UUID) sur POST /v1/images/generations, /v1/images/edits, /v1/videos et /v1/audio/speech :
- La première requête s’exécute normalement et sa réponse est conservée 24 heures.
- Une nouvelle tentative avec la même clé et le même corps récupère cette réponse, marquée de l’en-tête
Idempotency-Replayed: true. Rien n’est exécuté ni facturé. - La même clé avec un corps différent est refusée avec
409 idempotency_key_reused. - Une nouvelle tentative alors que la première requête est toujours en cours reçoit
409 idempotency_key_in_progressavecRetry-After. - Une réponse
5xxn’est pas conservée : une nouvelle tentative après une panne du fournisseur s’exécute à nouveau.
Les clés sont limitées à votre clé API. L’en-tête est ignoré sur tous les autres endpoints, et sur ceux ci-dessus une requête sans lui se comporte exactement comme avant. Les réponses de plus de 8 Mo ne sont pas conservées ; la requête s’exécute quand même, seule la relecture ne s’applique pas.
Erreurs en streaming
Avec "stream": true, une erreur survenue avant le premier token est une réponse d’erreur JSON ordinaire avec le statut ci-dessus. Une erreur survenue après le début du stream arrive sous la forme d’un dernier fragment data: dont le contenu est le même objet error, puis le stream se termine.
Un stream qui se termine trop tôt, parce que la connexion a été coupée, que vous vous êtes déconnecté ou qu’une erreur l’a interrompu, est facturé aux prix habituels du modèle pour :
- son prompt, en entier ;
- la sortie streamée jusque-là. Le raisonnement streamé par un modèle de raisonnement compte comme sortie, même si aucun texte de réponse n’était encore arrivé.
La sortie que le modèle a pu continuer à générer après l’arrêt du stream n’est pas facturée. Un stream terminé avant de produire la moindre sortie n’est pas facturé du tout.
Solde épuisé
Quand votre solde est inférieur ou égal à 0 $, toute requête vers un modèle payant est refusée avec 400 insufficient_credits. Rien n’est exécuté ni facturé. Ajoutez des crédits et réessayez. Les modèles dont l’ID se termine par :free fonctionnent toujours avec un solde de 0 $.
Quand votre solde est supérieur à 0 $ mais inférieur au coût maximal possible d’une requête, seule cette requête est refusée avec 400 request_exceeds_balance ; les requêtes plus petites passent toujours.
Le message indique ce que la requête pourrait coûter et quel est votre solde.
Baissez max_tokens ou raccourcissez l’entrée pour l’exécuter dans les limites de votre solde, ou ajoutez des crédits.
Migrer depuis OpenAI
Les corps d’erreur ont le même objet error que ceux d’OpenAI.
Deux cas répondent avec un statut ou un code différent :
| Cas | OpenAI | OceanToken |
|---|---|---|
| Solde épuisé | 429 insufficient_quota | 400 insufficient_credits |
| Modèle inconnu | 404 model_not_found | 400 model_not_found |
Les valeurs de code sont toutes les nôtres ; Codes les liste.
Quelques statuts ont un sens qui nous est propre : un modèle saturé renvoie 429 provider_overloaded (OpenAI répond 503), 408 timeout est un stream resté muet, 409 est un conflit d’idempotence, et 501 not_supported est un endpoint que nous ne proposons pas.
Si votre code lit 429 insufficient_quota comme un manque d’argent, cherchez plutôt 400 insufficient_credits ; ce n’est pas un 429, donc une boucle de nouvelles tentatives ne tourne pas dessus.
Un modèle inconnu est un 400 : un gestionnaire qui attend un 404 ne le voit pas ; basez-vous sur code et la vérification fonctionne dans les deux cas.
Politique de contenu
Les requêtes qui déclenchent un contrôle de sécurité sont refusées avec 400 content_filtered. Le contrôle a lieu avant l’appel au modèle : la requête n’est donc pas facturée.
Retrouver une requête échouée
Chaque échec est enregistré dans la console sous Logs, avec son statut et le corps de la réponse. Retrouvez-le par son heure et son modèle, et citez son x-oceantoken-request-id quand vous nous écrivez.