Gestion des erreurs
Une requête qui échoue renvoie un code de statut HTTP et un corps JSON avec un type d'erreur et un message. Cette page liste chaque statut, montre le corps d'erreur de chaque format d'API et indique quelles erreurs valent la peine d'être renvoyées.
Les SDK lèvent un objet d'erreur qui porte le code de statut et les champs du corps. Cette requête nomme un modèle qui n'existe pas :
import openai
from openai import OpenAI
client = OpenAI(
api_key="YOUR_API_KEY",
base_url="https://api.shannon-ai.com/v1",
)
try:
client.chat.completions.create(
model="no-such-model",
messages=[{"role": "user", "content": "Hello"}],
)
except openai.APIStatusError as error:
print(error.status_code) # 400
print(error.type) # invalid_request_error
print(error.body["message"]) # unknown model: no-such-model
print(error.response.headers.get("x-request-id")) # the id of this request import OpenAI from "openai";
const client = new OpenAI({
apiKey: "YOUR_API_KEY",
baseURL: "https://api.shannon-ai.com/v1",
});
try {
await client.chat.completions.create({
model: "no-such-model",
messages: [{ role: "user", content: "Hello" }],
});
} catch (error) {
if (!(error instanceof OpenAI.APIError)) throw error;
console.log(error.status); // 400
console.log(error.type); // invalid_request_error
console.log(error.error.message); // unknown model: no-such-model
} curl -i https://api.shannon-ai.com/v1/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "no-such-model",
"messages": [{"role": "user", "content": "Hello"}]
}' Elle renvoie le statut 400 et ce corps :
{
"error": {
"type": "invalid_request_error",
"message": "unknown model: no-such-model"
}
} Corps de l'erreur
Il y a deux corps. /v1/messages répond au format Anthropic ; tous les autres points de terminaison répondent au format OpenAI. Dans les deux, les détails sont dans error.type et error.message, de sorte qu'un seul lecteur sert tous les points de terminaison.
{
"error": {
"type": "authentication_error",
"message": "Invalid API key",
"code": "invalid_api_key",
"param": null
}
} {
"type": "error",
"error": {
"type": "authentication_error",
"message": "Invalid API key"
}
} | Champ | Type | Description |
|---|---|---|
error.type | string | Le genre d'erreur. Voir Types d'erreur ci-dessous. |
error.message | string | Une phrase pour une personne. Les textes des tableaux ci-dessous sont stables, hormis leurs parties variables, indiquées par <id>, <n> ou …. |
error.code | string | null | Présent sur certaines erreurs du format OpenAI : invalid_api_key avec 401, request_too_large avec 413, context_length_exceeded quand une conversation est trop longue, sinon null ou absent. Décidez d'abord d'après le statut et error.type, et traitez code comme un complément. |
error.param | null | Présent sur les mêmes erreurs que code. Toujours null. |
type | string | Format Anthropic uniquement. Toujours error. |
Types d'erreur
| Type | Statuts | Description |
|---|---|---|
invalid_request_error | 400, 413, 415, 422 | La requête elle-même est erronée : un champ, le corps, l'id du modèle ou une entrée que le modèle n'accepte pas. Modifiez la requête avant de la renvoyer. |
authentication_error | 401 | La clé API est absente, inconnue ou révoquée. |
not_found_error | 404 | Le chemin n'existe pas. |
rate_limit_error | 429 | Le solde ne couvre pas la requête, la protection anti-flood l'a arrêtée, ou le modèle est occupé. Le message indique laquelle. |
api_error | 500, 502, 503, 504 | La requête était correcte et la panne est de notre côté. Renvoyez-la. |
server_error | 500 | La requête était correcte et la panne est de notre côté. Renvoyez-la. |
Codes de statut
| Statut | Type | Message | Quand |
|---|---|---|---|
400 | invalid_request_error | Failed to parse the request body as JSON: … | Le corps n'est pas du JSON valide. |
400 | invalid_request_error | unknown model: <id> | model n'est pas un id publié. Sur /v1/responses, aussi quand model est absent. |
400 | invalid_request_error | No user message provided | Niveaux Shannon : la requête ne contient aucun texte utilisateur. |
400 | invalid_request_error | <id> does not accept image input | Une image a été envoyée à un modèle open-weight hébergé sans entrée image. |
400 | invalid_request_error | audio and video input are not supported by any hosted model | Une partie audio ou vidéo a été envoyée à un modèle open-weight hébergé. |
400 | invalid_request_error | <id> does not accept response_format | Une sortie structurée a été demandée à un modèle open-weight hébergé qui n'en a pas. |
400 | invalid_request_error | <id> does not accept response_format json_schema; use json_object | Un schéma JSON a été envoyé à un modèle open-weight hébergé qui n'accepte que json_object. |
400 | invalid_request_error | unknown reasoning effort '<value>'; expected off, low, medium or high | L'effort de raisonnement n'est pas l'une des valeurs listées. Modèles open-weight hébergés. |
400 | invalid_request_error | count_tokens is available for the hosted open models; unknown model: <id> | Le comptage de tokens a été demandé pour un modèle qui n'est pas un modèle open-weight hébergé. |
401 | authentication_error | Missing authentication | Aucune clé API n'a été envoyée. |
401 | authentication_error | Invalid API key | La clé est inconnue, a été remplacée par une plus récente, ou a été révoquée. |
404 | not_found_error | Not Found | Le chemin n'existe pas, par exemple /v1/embeddings. |
405 | api_error | Method Not Allowed | Le chemin existe mais pas avec cette méthode HTTP. |
413 | invalid_request_error | Failed to buffer the request body: … | Le corps de la requête dépasse 32 MiB. |
415 | invalid_request_error | Expected request with `Content-Type: application/json` | L'en-tête Content-Type est absent ou n'est pas application/json. |
422 | invalid_request_error | Failed to deserialize the JSON body into the target type: … | Un champ obligatoire est absent, comme messages, ou un champ a un mauvais type JSON. |
429 | rate_limit_error | Quota exceeded. Upgrade your plan at shannon-ai.com/plan | La limite de sortie de la requête est supérieure à ce qui reste de votre solde. |
429 | rate_limit_error | Too many requests. Retry in <n>s. | Protection anti-flood : plus de 120 requêtes en une minute sur votre compte. <n> est le nombre de secondes jusqu'à la fin de la minute. |
429 | rate_limit_error | Shannon Coder call quota reached. Upgrade your plan at shannon-ai.com/plan | shannon-coder-1 sur /v1/chat/completions et /v1/messages : les appels Shannon Coder de votre forfait pour la fenêtre actuelle de 4 heures sont épuisés. |
429 | rate_limit_error | Shannon routes are temporarily busy. Please retry. | Le modèle est occupé en ce moment. |
500 | server_error | The model backend failed to answer. Please retry. | Le modèle n'a pas produit de réponse. |
500 | server_error | An internal error occurred. Please retry. | Une défaillance interne pendant la préparation de la requête. |
502 | api_error | The model backend failed to answer. Please retry. | Le modèle n'a pas produit de réponse. Famille Shannon 3 et modèles open-weight hébergés. |
503 | api_error | Could not verify your quota right now. Please retry. | Votre solde n'a pas pu être lu en ce moment. |
503 | api_error | token counting is temporarily unavailable for this model | Le comptage de tokens n'est pas disponible pour ce modèle en ce moment. |
504 | api_error | upstream timed out | Un modèle open-weight hébergé a dépassé la limite de temps d'une requête hors streaming. |
Les vérifications s'exécutent dans cet ordre : clé API, corps de la requête, id du modèle, protection anti-flood, solde. Une requête avec une mauvaise clé et un mauvais corps renvoie donc 401. Une requête refusée avec un statut 4xx n'est pas facturée.
Solde et protection anti-flood
Chaque requête est servie de façon égale : il n'y a ni niveaux de débit ni quota API distinct. Deux choses peuvent tout de même répondre 429, toutes deux avec le type rate_limit_error. Le message les distingue.
Solde
Votre solde est le forfait du jour, qui est réinitialisé à 00:00 UTC, puis vos crédits achetés. Le chat et l'API puisent dans le même solde.
Chaque requête réserve d'abord sa limite de sortie : max_tokens, ou max_output_tokens sur /v1/responses, 4,096 si vous n'envoyez ni l'un ni l'autre. Quand la réponse est terminée, le montant réservé est remplacé par ce que la requête a utilisé. Si le montant ne tient pas dans le solde restant, la requête renvoie Quota exceeded avant que le modèle démarre.
Les requêtes qui s'exécutent en même temps réservent chacune leur propre montant. De nombreuses requêtes parallèles avec un max_tokens élevé peuvent donc épuiser le solde un instant, même si chaque réponse est courte. Un max_tokens plus bas réserve moins.
Protection anti-flood
La protection anti-flood autorise 120 requêtes par minute et par compte. Elle compte le compte, pas la clé, sur /v1/chat/completions, /v1/responses et /v1/messages ensemble. La minute commence avec votre première requête.
La requête numéro 121 dans cette minute renvoie Too many requests. Retry in <n>s. et ne coûte rien. Les requêtes refusées comptent aussi : un client qui renvoie sans attendre reste donc refusé jusqu'à la fin de la minute. Attendez les <n> secondes indiquées dans le message.
Les requêtes envoyées en parallèle ne sont pas refusées parce qu'elles sont parallèles. Elles attendent en file et reçoivent une réponse à tour de rôle.
GET /v1/models et les points de terminaison de comptage de tokens ne sont pas comptés.
Plus d'informations sur les limites et le solde, et où voir votre propre solde et votre utilisation : Limites et solde Clés et utilisation
Que réessayer
Ne renvoyez une requête que si la même requête peut réussir. Les SDK OpenAI et Anthropic réessaient déjà deux fois d'eux-mêmes les réponses 429 et 5xx ; réglez max_retries (maxRetries en JavaScript) pour changer cela.
| Erreur | Renvoyer ? | Comment |
|---|---|---|
400, 404, 405, 413, 415, 422 | Non. Corrigez d'abord la requête. | Le message nomme le champ, le modèle ou l'entrée qui est erroné. |
401 | Non. Utilisez une clé valide. | Créez ou remplacez la clé sur la page Clés et utilisation. |
429 Too many requests | Oui, après l'attente. | Attendez le nombre de secondes indiqué dans le message, puis envoyez la même requête. |
429 Quota exceeded | Pas en l'état. | Baissez max_tokens, attendez la réinitialisation quotidienne à 00:00 UTC, ou ajoutez des crédits. |
429 Shannon Coder call quota reached | Oui, dans la fenêtre suivante. | Les fenêtres démarrent toutes les 4 heures à partir de 00:00 UTC. Un autre modèle répond immédiatement. |
429 … temporarily busy | Oui. | Attendez quelques secondes, puis plus longtemps à chaque nouvelle tentative. |
500, 502, 503 | Oui. | Attendez 1 seconde, puis 2, 4 et 8. Arrêtez-vous après quelques tentatives et signalez l'id de la requête. |
504 | Oui. | Envoyez-la avec stream à true : la limite de temps s'applique aux réponses hors streaming. |
Une boucle de réessai qui suit ce tableau :
import re
import time
import openai
from openai import OpenAI
# max_retries=0: this loop does the retrying, the SDK does not add its own.
client = OpenAI(api_key="YOUR_API_KEY", base_url="https://api.shannon-ai.com/v1", max_retries=0)
def create_with_retry(attempts=5, **request):
for attempt in range(attempts):
try:
return client.chat.completions.create(**request)
except openai.RateLimitError as error:
text = str(error)
flood = re.search(r"Retry in (\d+)s", text)
if flood:
time.sleep(int(flood.group(1))) # flood protection: wait out the minute
elif "temporarily busy" in text:
time.sleep(2 ** attempt)
else:
raise # balance: a wait of seconds does not help
except (openai.InternalServerError, openai.APIConnectionError):
time.sleep(2 ** attempt) # 1, 2, 4, 8 seconds
raise RuntimeError("the request failed after all attempts")
response = create_with_retry(
model="shannon-3",
messages=[{"role": "user", "content": "Hello"}],
)
print(response.choices[0].message.content) import OpenAI from "openai";
// maxRetries: 0 — this loop does the retrying, the SDK does not add its own.
const client = new OpenAI({ apiKey: "YOUR_API_KEY", baseURL: "https://api.shannon-ai.com/v1", maxRetries: 0 });
const sleep = (seconds) => new Promise((resolve) => setTimeout(resolve, seconds * 1000));
async function createWithRetry(request, attempts = 5) {
for (let attempt = 0; attempt < attempts; attempt++) {
try {
return await client.chat.completions.create(request);
} catch (error) {
const text = String(error.message);
const flood = text.match(/Retry in (\d+)s/);
if (error.status === 429 && flood) {
await sleep(Number(flood[1])); // flood protection: wait out the minute
} else if (error.status === 429 && text.includes("temporarily busy")) {
await sleep(2 ** attempt);
} else if (error.status >= 500 || error instanceof OpenAI.APIConnectionError) {
await sleep(2 ** attempt); // 1, 2, 4, 8 seconds
} else {
throw error; // wrong request, wrong key or balance
}
}
}
throw new Error("the request failed after all attempts");
}
const response = await createWithRetry({
model: "shannon-3",
messages: [{ role: "user", content: "Hello" }],
});
console.log(response.choices[0].message.content); Erreurs dans un flux
Un flux a le statut 200 dès son premier octet. Un échec après cela arrive comme un événement dans le flux : un chunk avec un membre error sur /v1/chat/completions, un événement error sur /v1/messages, un événement response.failed ou error sur /v1/responses. Les événements de clôture du flux suivent quand même. Considérez une telle réponse comme échouée et renvoyez la requête, comme pour un statut 5xx.
Une conversation plus longue que la fenêtre de contexte du modèle est signalée de cette façon, avec le type invalid_request_error et le code context_length_exceeded. Raccourcissez la conversation avant de la renvoyer.
Les trames exactes sont décrites avec le streaming. Diffusion en continu
Ids de requête
Chaque réponse, erreurs comprises, a un en-tête x-request-id. C'est la valeur que vous avez envoyée dans votre propre en-tête x-request-id, ou 12 caractères hexadécimaux si vous n'en avez envoyé aucune. Journalisez-la, et citez-la quand vous demandez de l'aide pour une requête.