Quando qualcosa va storto
Cerca un errore
#Incolla l'errore
Leggi gli errori in quest'ordine: stato HTTP, poi error.type, poi error.code. Lo stato ha sempre un significato; type ti dice chi ha rifiutato la chiamata; code è stabile solo per gli errori generati dal gateway stesso.
#Come gestire i casi
- 1
Decidi in base allo stato
402 significa ricaricare, 403 ambito della chiave, 429 attendere, 5xx riprovare. Questo livello non mente mai e non cambia mai.
- 2
Controlla error.type
xiaoxiang_error è nostro. Qualsiasi altro — openai_error, claude_error, gemini_error — è il corpo del fornitore, inoltrato invariato.
- 3
Poi leggi error.code
Decidi in base a code solo per xiaoxiang_error. I codici upstream appartengono al fornitore e possono cambiare senza preavviso.
Non tutti gli errori hanno un corpo JSON
Strutture degli errori
Al tuo client arrivano cinque forme. Gestiscile tutte e cinque — le ultime tre sono quelle che le integrazioni di solito dimenticano.
Errore del gateway
Il gateway stesso ha rifiutato la chiamata — autenticazione, quota, routing, forma della richiesta.
{
"error": {
"message": "insufficient account balance, please top up to continue (request id: 20260910...)",
"type": "xiaoxiang_error",
"code": "insufficient_user_quota"
}
}Errore upstream, inoltrato così com'è
Il fornitore ha risposto con un proprio errore. Il corpo viene inoltrato invariato, quindi type e code sono quelli del fornitore.
{
"error": {
"message": "This model's maximum context length is 128000 tokens.",
"type": "invalid_request_error",
"code": "context_length_exceeded"
}
}Rifiuto da controllo preliminare
L'idempotenza e alcuni altri controlli preliminari rispondono prima che esista l'envelope del relay. Struttura piatta, code in maiuscolo.
{
"code": "IDEMPOTENCY_IN_PROGRESS",
"error": "a request with this Idempotency-Key is still processing"
}Rifiuto di task asincrono
Ogni endpoint dei task — video, task immagine, musica, 3D — riporta gli errori in una propria struttura piatta. Qui non c'è un oggetto error annidato, quindi un parser scritto per /v1/chat/completions legge null.
{
"code": "task_not_exist",
"message": "task does not exist",
"data": null
}Solo stato, corpo vuoto
I rifiuti per limite di frequenza e i corpi di richiesta troppo grandi terminano con il solo stato.
HTTP/1.1 429 Too Many Requests
Retry-After: 20
Content-Length: 0Header di risposta
Registra l'ID richiesta di ogni risposta, non solo degli errori. È l'identificativo con cui l'assistenza rintraccia una chiamata, e fa la differenza tra una risposta in cinque minuti e una giornata di congetture.
| Header | Inviato quando | Come usarlo |
|---|---|---|
| X-Oneapi-Request-Id | Ogni risposta, di successo o di errore. | Registralo accanto al tuo trace id. L'assistenza risponde ai ticket partendo da questo valore, e lo stesso id viene aggiunto ai messaggi di errore del gateway. |
| Retry-After | Con 429, e con 409 mentre una Idempotency-Key è ancora in elaborazione. | Attendi almeno questo numero di secondi. È l'unica indicazione di backoff che il gateway invia. |
| X-Idempotent-Replayed | Per una Idempotency-Key ripetuta viene riprodotta una risposta salvata. | true significa che non è stato eseguito nuovo lavoro e non è stato addebitato nulla di nuovo — tratta il corpo come il risultato originale. |
| X-Idempotency-Sealed | La risposta originale non può essere riprodotta in sicurezza (per esempio uno stream o un corpo troppo grande). | Smetti di riprovare con quella chiave. Riconcilia invece tramite l'endpoint del task o i tuoi registri. |
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);
}#Tabella dei codici di errore
Errori generati dal gateway stesso, sempre con type "xiaoxiang_error". Due righe condividono lo stato 429 ma significano cose opposte — leggi il code prima di applicare il backoff.
| HTTP | Codice | Significato | Cosa fare | Riprovare? |
|---|---|---|---|---|
| 401 | nessuno | Bearer token mancante, malformato, eliminato o disattivato. Questo livello non imposta alcun code — basati sullo stato. | Controlla l'header Authorization e lo stato della chiave in Console → Chiavi API. | no |
| 401 | signature_required | La chiave ha la firma delle richieste attiva e alla richiesta mancano X-Timestamp, X-Nonce o X-Signature. | Firma ogni richiesta fatta con questa chiave (Autenticazione e chiavi → Firma delle richieste), oppure disattiva la firma sulla chiave. | no |
| 401 | signature_expired | X-Timestamp non è in secondi Unix a 10 cifre o si discosta di oltre 5 minuti dall'ora del server. La risposta include X-Server-Timestamp. | Correggi l'orologio del client in base alla differenza con X-Server-Timestamp e firma di nuovo con un nuovo nonce. | sì |
| 401 | signature_invalid | X-Signature non corrisponde al segreto di firma della chiave, oppure X-Nonce è malformato. | Firma timestamp, nonce, metodo e percorso (senza query string) uniti da a capo, con il segreto attuale preso da Console → Chiavi API. | no |
| 401 | signature_replayed | Questo X-Nonce è già stato usato con questa chiave negli ultimi 10 minuti. | Genera un nuovo nonce casuale per ogni richiesta, inclusi i nuovi tentativi. | sì |
| 403 | access_denied | L'IP chiamante non è nella lista di IP consentiti della chiave. | Aggiungi l'indirizzo di uscita in Console → Chiavi API, oppure rimuovi la lista consentiti per le chiavi senza IP fisso. | no |
| 403 | nessuno | Account sospeso, oppure la chiave non può usare questo modello o canale. | Controlla prima la lista dei modelli consentiti della chiave; se non ha restrizioni, apri un ticket — è l'account stesso a essere bloccato. | no |
| 400 | unsupported_endpoint | Il modello esiste ma non serve questo percorso. Il messaggio elenca gli endpoint che serve. | Sposta la chiamata su un endpoint elencato — i modelli di task immagine non rispondono su /v1/chat/completions. | no |
| 400 | read_request_body_failed | Impossibile leggere il corpo della richiesta — upload troncato, Content-Type errato o uno stream terminato in anticipo. | Invia un corpo completo con un Content-Type coerente. Reinviare la stessa richiesta difettosa fallisce allo stesso modo. | no |
| 402 | insufficient_user_quota | Il saldo del portafoglio non copre l'importo pre-riservato per questa chiamata. | Ricarica in Console → Portafoglio, poi riprova. | no |
| 429 | insufficient_user_quota | È stato raggiunto il limite di spesa giornaliero dell'account. Stesso stato della limitazione di frequenza, significato opposto — il backoff non lo risolve. | Aumenta o rimuovi il limite nelle impostazioni dell'account, oppure riprendi domani. Decidi in base al code, non al solo 429. | no |
| 429 | nessuno | Limitazione RPM/RPD per chiave o controllo di concorrenza. Il corpo è vuoto; Retry-After indica l'attesa. | Attendi Retry-After secondi, poi riprova con backoff esponenziale e jitter. | sì |
| 413 | read_request_body_failed | Il corpo della richiesta supera la dimensione accettata. Il limite a livello di trasporto termina con un corpo vuoto. | Riduci il payload — carica i file tramite /v1/media/upload-policy e invia l'URL invece del Base64. | no |
| 422 | invalid_request | Nessun canale supporta questo intento multimediale per il modello — per esempio da immagine a video su una route solo da testo a video. | Controlla gli intenti supportati nella scheda del modello e cambia modello o intento. | no |
| 503 | model_not_found | Nome del modello sconosciuto, oppure nessun canale funzionante lo serve al momento. Nota lo stato: un modello sconosciuto restituisce 503, non 404. | Verifica il nome su /v1/models, poi riprova — di solito il failover trova un canale funzionante al tentativo successivo. | sì |
| 500 | sensitive_words_detected | Il prompt è stato bloccato dal filtro dei contenuti prima di lasciare il gateway. | Modifica il prompt. Riprovare con lo stesso testo fallisce sempre. | no |
| 500 | count_token_failed | Il gateway non è riuscito a stimare i token per il prezzo, di solito a causa di un array di messaggi malformato. | Convalida l'array messages; se è ben formato, riprova e apri un ticket con l'id della richiesta. | sì |
| 500 | invalid_request | Impossibile interpretare la richiesta in una forma accettata da questo endpoint. Nota lo stato — un corpo che il gateway non riesce a leggere viene segnalato come 500, non 400. | Convalida il JSON rispetto al riferimento dell'endpoint prima di riprovare. | no |
| 502 | do_request_failed | Il fornitore non era raggiungibile, oppure ha restituito un errore che il gateway non è riuscito a recuperare con il failover. | Riprova con backoff — il tentativo successivo di solito finisce su un canale diverso. | sì |
| 504 | do_request_failed | Il fornitore non ha risposto entro la scadenza del gateway. | Riprova con backoff; i prompt lunghi e i fornitori sotto carico vanno più spesso in timeout. | sì |
| 499 | do_request_failed | Il tuo client ha chiuso la connessione prima dell'arrivo della risposta — di solito è il tuo timeout HTTP che scatta a metà dello stream. | Aumenta il timeout di lettura del client. Le generazioni lunghe richiedono minuti, non il valore predefinito della maggior parte delle librerie HTTP. | no |
Errori del controllo di idempotenza
Provengono dal controllo anti-replay che precede il relay, quindi usano la forma piatta {code, error} con code in maiuscolo invece della struttura OpenAI.
| HTTP | Codice | Significato | Cosa fare | Riprovare? |
|---|---|---|---|---|
| 400 | IDEMPOTENCY_STREAM_UNSUPPORTED | Idempotency-Key inviata su una richiesta in streaming. | Rimuovi l'header per le chiamate in streaming, oppure imposta stream:false quando ti serve la protezione da replay. | no |
| 400 | IDEMPOTENCY_BODY_UNREADABLE | Impossibile calcolare l'hash del corpo per il registro di idempotenza. | Invia un normale corpo JSON o multipart — i corpi chunked che non possono essere bufferizzati vengono rifiutati. | no |
| 409 | IDEMPOTENCY_PAYLOAD_CONFLICT | Questa chiave è già stata usata con un corpo, un percorso o una query diversi. | Genera una nuova chiave. Una chiave identifica una singola richiesta esatta, non un ciclo di tentativi. | no |
| 409 | IDEMPOTENCY_IN_PROGRESS | La prima richiesta con questa chiave è ancora in esecuzione. | Attendi Retry-After (2s) e riprova con la stessa chiave — riceverai la risposta originale. | sì |
| 409 | IDEMPOTENCY_RESULT_UNREPLAYABLE | La richiesta originale è terminata, ma la sua risposta è stata sigillata e non può essere riprodotta. | Non reinviare alla cieca. Riconcilia prima tramite l'endpoint del task o i tuoi registri. | no |
| 503 | IDEMPOTENCY_UNAVAILABLE | Il registro di idempotenza non è disponibile, quindi il controllo blocca la richiesta invece di rischiare un doppio addebito. | Riprova con backoff — la richiesta non è stata eseguita. | sì |
Errori dei task asincroni
Gli endpoint di video, task immagine, musica e 3D rispondono con una propria forma piatta — {code, message, data} — sia per l'invio sia per il polling e l'annullamento. Una particolarità da prevedere: interrogare un id di task sconosciuto restituisce 400, annullarlo 404.
| HTTP | Codice | Significato | Cosa fare | Riprovare? |
|---|---|---|---|---|
| 400 | task_not_exist | Polling o remix di un id di task che non esiste o appartiene a un altro account. Nota lo stato — interrogare un task sconosciuto restituisce 400, mentre annullarlo restituisce 404. | Salva task_id al momento dell'invio ed esegui il polling esattamente con quel valore. | no |
| 400 | invalid_request | Manca un campo obbligatorio — prompt in un remix, task_id in un recupero. | Correggi il corpo o il percorso prima di riprovare. | no |
| 400 | task_channel_disable | Un task successivo (remix, extend) è vincolato al canale del task originale, e quel canale ora è disattivato. | Invia un nuovo task invece di derivarlo da quello vecchio. | no |
| 400 | invalid_source_task | Il task di origine è del tipo sbagliato per questa operazione — per esempio un id video passato a una continuazione 3D. | Verifica che l'id di origine provenga dalla stessa famiglia di funzionalità. | no |
| 404 | task_not_exist | Annullamento di un id di task che non esiste o non è tuo. | Niente da annullare — consideralo già eliminato. | no |
| 409 | task_not_cancellable | Il task ha già raggiunto uno stato finale, quindi non c'è nulla da annullare. | Interroga ancora una volta e usa il risultato che hai già pagato. | no |
| 501 | task_cancel_not_supported | Il provider dietro questo task non espone un'API di annullamento. La maggior parte non lo fa — l'annullamento è l'eccezione, non la regola. | Lascia che il task finisca. Progetta la UI in modo che l'annullamento sia best-effort, non una garanzia. | no |
| 502 | cancel_task_failed | Il provider non era raggiungibile durante l'annullamento. Il task è invariato. | Riprova una volta, poi lascia che finisca. | sì |
Non hai ancora risolto?
Inviaci l'ID richiesta o il messaggio di errore e verificheremo.