En cas de problème

Rechercher une erreur

#Coller l'erreur

Lisez les erreurs dans cet ordre : statut HTTP, puis error.type, puis error.code. Le statut est toujours significatif ; type indique qui a rejeté l'appel ; code n'est stable que pour les erreurs émises par la passerelle elle-même.

#Comment réagir

  1. 1

    Commencez par le statut

    402 : rechargez ; 403 : portée de la clé ; 429 : attendez ; 5xx : réessayez. Cette couche ne ment jamais et ne change jamais.

  2. 2

    Vérifiez error.type

    xiaoxiang_error est le nôtre. Tout le reste — openai_error, claude_error, gemini_error — est le corps propre au fournisseur, transmis tel quel.

  3. 3

    Puis lisez error.code

    Ne vous basez sur code que pour xiaoxiang_error. Les codes en amont appartiennent au fournisseur et peuvent changer sans préavis.

Toutes les erreurs n'ont pas de corps JSON

Les rejets pour limite de débit et les corps trop volumineux s'interrompent avec une simple ligne de statut. Un parseur qui suppose que response.json() réussit toujours lèvera une exception sur le chemin d'erreur au lieu de le gérer.
Enveloppes d'erreur

Cinq formats atteignent votre client. Gérez-les tous les cinq — les trois derniers sont ceux que les intégrations oublient le plus souvent.

Erreur de la passerelle

La passerelle elle-même a rejeté l'appel — authentification, quota, routage, format de la requête.

http
{
  "error": {
    "message": "insufficient account balance, please top up to continue (request id: 20260910...)",
    "type": "xiaoxiang_error",
    "code": "insufficient_user_quota"
  }
}

Erreur en amont, transmise telle quelle

Le fournisseur a répondu avec sa propre erreur. Le corps est transmis sans modification : type et code sont ceux du fournisseur.

http
{
  "error": {
    "message": "This model's maximum context length is 128000 tokens.",
    "type": "invalid_request_error",
    "code": "context_length_exceeded"
  }
}

Rejet par un garde-fou

L'idempotence et quelques autres garde-fous préalables répondent avant que l'enveloppe du relais n'existe. Format plat, code en majuscules.

http
{
  "code": "IDEMPOTENCY_IN_PROGRESS",
  "error": "a request with this Idempotency-Key is still processing"
}

Rejet de tâche asynchrone

Chaque endpoint de tâche — vidéo, tâche d'image, musique, 3D — signale les échecs dans son propre format plat. Il n'y a pas d'objet error imbriqué ici : un parseur écrit pour /v1/chat/completions lira null.

http
{
  "code": "task_not_exist",
  "message": "task does not exist",
  "data": null
}

Statut seul, corps vide

Les rejets pour limite de débit et les corps de requête trop volumineux s'interrompent avec un simple statut.

http
HTTP/1.1 429 Too Many Requests
Retry-After: 20
Content-Length: 0
En-têtes de réponse

Journalisez l'ID de requête de chaque réponse, pas seulement des échecs. C'est l'identifiant qui permet au support de retracer un appel, et il fait la différence entre une réponse en cinq minutes et une journée de suppositions.

En-têteEnvoyé quandUtilisation
X-Oneapi-Request-IdChaque réponse, succès ou erreur.Enregistrez-le avec votre propre identifiant de trace. Le support traite les tickets à partir de cette valeur, et ce même identifiant est ajouté aux messages d'erreur de la passerelle.
Retry-AfterEn cas de 429, et de 409 tant qu'une requête avec la même Idempotency-Key est encore en cours.Attendez au moins ce nombre de secondes. C'est la seule indication de backoff envoyée par la passerelle.
X-Idempotent-ReplayedLa réponse stockée est rejouée lorsqu'une Idempotency-Key est réutilisée.true signifie qu'aucun nouveau traitement n'a eu lieu et que rien n'a été refacturé — traitez le corps comme le résultat d'origine.
X-Idempotency-SealedLa réponse d'origine ne peut pas être rejouée sans risque (par exemple un stream ou un corps trop volumineux).Cessez de réessayer avec cette clé. Faites plutôt le rapprochement via l'endpoint de la tâche ou vos propres enregistrements.
log-the-request-id.ts
const response = await fetch(url, init);
const requestId = response.headers.get("X-Oneapi-Request-Id");

if (!response.ok) {
  // Bodies are absent on 429 and 413 — never assume JSON here.
  const detail = await response.text();
  logger.error({ status: response.status, requestId, detail });
  throw new GatewayError(response.status, requestId, detail);
}

#Table des codes d'erreur

Erreurs émises par la passerelle elle-même, toujours avec le type "xiaoxiang_error". Deux lignes partagent le statut 429 avec des sens opposés — lisez le code avant d'appliquer un backoff.

HTTPCodeSignificationQue faireRéessayer ?
401aucunJeton bearer absent, mal formé, supprimé ou désactivé. Cette couche ne définit aucun code — basez-vous sur le statut.Vérifiez l'en-tête Authorization et l'état de la clé dans Console → Clés API.non
401signature_requiredLa signature des requêtes est activée sur la clé et la requête ne contient pas X-Timestamp, X-Nonce ou X-Signature.Signez chaque requête effectuée avec cette clé (Authentification et clés → Signature des requêtes), ou désactivez la signature sur la clé.non
401signature_expiredX-Timestamp n'est pas en secondes Unix sur 10 chiffres ou s'écarte de plus de 5 minutes de l'heure du serveur. La réponse contient X-Server-Timestamp.Corrigez l'horloge du client de l'écart indiqué par X-Server-Timestamp et signez de nouveau avec un nouveau nonce.oui
401signature_invalidX-Signature ne correspond pas au secret de signature de la clé, ou X-Nonce est mal formé.Signez l'horodatage, le nonce, la méthode et le chemin (sans paramètres de requête), joints par des sauts de ligne, avec le secret actuel indiqué dans Console → Clés API.non
401signature_replayedCe X-Nonce a déjà été utilisé avec cette clé au cours des 10 dernières minutes.Générez un nouveau nonce aléatoire pour chaque requête, y compris les nouvelles tentatives.oui
403access_deniedL'IP de l'appelant ne figure pas dans la liste d'IP autorisées de la clé.Ajoutez l'adresse de sortie dans Console → Clés API, ou supprimez la liste d'autorisation pour les clés sans IP fixe.non
403aucunCompte suspendu, ou la clé n'est pas autorisée à utiliser ce modèle ou ce canal.Vérifiez d'abord la liste des modèles autorisés de la clé ; si elle n'est pas restreinte, ouvrez un ticket — c'est le compte lui-même qui est bloqué.non
400unsupported_endpointLe modèle existe mais ne prend pas en charge ce chemin. Le message liste les endpoints qu'il prend en charge.Déplacez l'appel vers un endpoint listé — les modèles de tâches d'image ne répondent pas sur /v1/chat/completions.non
400read_request_body_failedLe corps de la requête n'a pas pu être lu — envoi tronqué, Content-Type incorrect ou flux interrompu prématurément.Envoyez un corps complet avec un Content-Type correspondant. Renvoyer la même requête défectueuse échouera de la même façon.non
402insufficient_user_quotaLe solde du portefeuille ne couvre pas le montant préréservé pour cet appel.Rechargez dans Console → Portefeuille, puis réessayez.non
429insufficient_user_quotaLe plafond de dépenses quotidien du compte est atteint. Même statut que la limitation de débit, sens opposé — le backoff ne le débloquera pas.Augmentez ou supprimez le plafond dans les paramètres du compte, ou reprenez demain. Basez-vous sur le code, pas sur le seul 429.non
429aucunLimitation RPM/RPD par clé ou garde-fou de concurrence. Le corps est vide ; Retry-After indique le délai d'attente.Attendez Retry-After secondes, puis réessayez avec un backoff exponentiel et du jitter.oui
413read_request_body_failedLe corps de la requête dépasse la taille acceptée. La limite au niveau transport interrompt la requête avec un corps vide.Réduisez la charge utile — envoyez les fichiers via /v1/media/upload-policy et transmettez l'URL au lieu du Base64.non
422invalid_requestAucun canal ne prend en charge ce mode de génération pour le modèle — par exemple image vers vidéo sur une route uniquement texte vers vidéo.Consultez la fiche du modèle pour les modes pris en charge, puis changez de modèle ou de mode.non
503model_not_foundNom de modèle inconnu, ou aucun canal opérationnel ne le sert actuellement. Attention au statut : un modèle inconnu renvoie 503, pas 404.Vérifiez le nom avec /v1/models, puis réessayez — le basculement trouve généralement un canal opérationnel à la tentative suivante.oui
500sensitive_words_detectedLe prompt a été bloqué par le filtre de contenu avant de quitter la passerelle.Modifiez le prompt. Réessayer avec le même texte échoue systématiquement.non
500count_token_failedLa passerelle n'a pas pu estimer les tokens pour la tarification, généralement à cause d'un tableau de messages mal formé.Validez le tableau messages ; s'il est bien formé, réessayez et ouvrez un ticket avec l'ID de requête.oui
500invalid_requestLa requête n'a pas pu être interprétée dans un format accepté par cet endpoint. Attention au statut — un corps illisible par la passerelle est signalé en 500, pas en 400.Validez le JSON par rapport à la référence de l'endpoint avant de réessayer.non
502do_request_failedLe fournisseur était injoignable, ou a renvoyé un échec que la passerelle n'a pas pu rattraper par basculement.Réessayez avec backoff — la tentative suivante passe généralement par un autre canal.oui
504do_request_failedLe fournisseur n'a pas répondu dans le délai imparti par la passerelle.Réessayez avec backoff ; les prompts longs et les fournisseurs saturés expirent plus souvent.oui
499do_request_failedVotre client a fermé la connexion avant l'arrivée de la réponse — généralement votre propre délai d'expiration HTTP qui se déclenche en plein stream.Augmentez le délai de lecture du client. Les longues générations prennent plusieurs minutes, bien plus que la valeur par défaut de la plupart des bibliothèques HTTP.non
Erreurs du garde-fou d'idempotence

Elles proviennent du garde-fou de rejeu placé devant le relais : elles utilisent donc le format plat {code, error} avec un code en majuscules au lieu de l'enveloppe OpenAI.

HTTPCodeSignificationQue faireRéessayer ?
400IDEMPOTENCY_STREAM_UNSUPPORTEDUne Idempotency-Key a été envoyée sur une requête en streaming.Retirez l'en-tête pour les appels en streaming, ou définissez stream:false si vous avez besoin de la protection par rejeu.non
400IDEMPOTENCY_BODY_UNREADABLELe corps n'a pas pu être haché pour le registre d'idempotence.Envoyez un corps JSON ou multipart classique — les corps chunked qui ne peuvent pas être mis en mémoire tampon sont rejetés.non
409IDEMPOTENCY_PAYLOAD_CONFLICTCette clé a déjà été utilisée avec un corps, un chemin ou des paramètres de requête différents.Générez une nouvelle clé. Une clé identifie une requête précise, pas une boucle de tentatives.non
409IDEMPOTENCY_IN_PROGRESSLa première requête avec cette clé est toujours en cours.Attendez Retry-After (2 s) et réessayez avec la même clé — vous obtiendrez la réponse d'origine.oui
409IDEMPOTENCY_RESULT_UNREPLAYABLELa requête d'origine est terminée, mais sa réponse a été scellée et ne peut pas être rejouée.Ne renvoyez pas la requête à l'aveugle. Faites d'abord le rapprochement via l'endpoint de la tâche ou vos propres enregistrements.non
503IDEMPOTENCY_UNAVAILABLELe registre d'idempotence est indisponible : le garde-fou refuse la requête plutôt que de risquer un double débit.Réessayez avec backoff — la requête n'a pas été exécutée.oui

Fonctionnement des clés d'idempotence →

Erreurs de tâches asynchrones

Les endpoints de vidéo, de tâche d'image, de musique et 3D répondent avec leur propre format plat — {code, message, data} — pour la soumission, l'interrogation comme l'annulation. Une particularité à prévoir : interroger un task id inconnu renvoie 400, l'annuler renvoie 404.

HTTPCodeSignificationQue faireRéessayer ?
400task_not_existInterrogation ou remix d'un task id inexistant ou appartenant à un autre compte. Attention au statut — interroger une tâche inconnue renvoie 400, alors que l'annuler renvoie 404.Enregistrez task_id lors de la soumission et interrogez avec exactement cette valeur.non
400invalid_requestUn champ obligatoire est manquant — prompt pour un remix, task_id pour une récupération.Corrigez le corps ou le chemin avant de réessayer.non
400task_channel_disableUne tâche dérivée (remix, extension) est liée au canal de la tâche d'origine, et ce canal est désormais désactivé.Soumettez une nouvelle tâche au lieu de dériver de l'ancienne.non
400invalid_source_taskLa tâche source n'est pas du bon type pour cette opération — par exemple un id vidéo passé à une continuation 3D.Vérifiez que l'id source provient de la même famille de capacités.non
404task_not_existAnnulation d'un task id inexistant ou qui ne vous appartient pas.Rien à annuler — considérez qu'elle n'existe plus.non
409task_not_cancellableLa tâche a déjà atteint un état final : il n'y a rien à annuler.Interrogez une dernière fois et utilisez le résultat déjà payé.non
501task_cancel_not_supportedLe fournisseur derrière cette tâche n'expose aucune API d'annulation. C'est le cas de la plupart — l'annulation est l'exception, pas la règle.Laissez la tâche se terminer. Concevez l'interface pour que l'annulation soit une tentative, pas une garantie.non
502cancel_task_failedLe fournisseur était injoignable pendant l'annulation. La tâche est inchangée.Réessayez une fois, puis laissez-la se terminer.oui

Cycle de vie d'une tâche asynchrone →

Toujours bloqué ?

Envoyez-nous l'ID de requête ou le message d'erreur, et nous examinerons le problème.