Quando algo dá errado
Consultar um erro
#Cole o erro
Leia os erros nesta ordem: status HTTP, depois error.type, depois error.code. O status sempre tem significado; type diz quem rejeitou a chamada; code só é estável para erros gerados pelo próprio gateway.
#Como tratar cada caso
- 1
Decida pelo status
402 significa recarregar, 403 significa escopo da chave, 429 significa aguardar, 5xx significa tentar de novo. Esta camada nunca mente e nunca muda.
- 2
Verifique error.type
xiaoxiang_error é nosso. Qualquer outro — openai_error, claude_error, gemini_error — é o corpo do próprio provedor, encaminhado sem alteração.
- 3
Depois leia error.code
Só decida pelo code em xiaoxiang_error. Os códigos do upstream pertencem ao provedor e podem mudar sem aviso.
Nem toda falha tem corpo JSON
Envelopes de erro
Cinco formatos chegam ao seu cliente. Trate os cinco — os três últimos são os que as integrações costumam esquecer.
Erro do gateway
O próprio gateway rejeitou a chamada — autenticação, cota, roteamento, formato da requisição.
{
"error": {
"message": "insufficient account balance, please top up to continue (request id: 20260910...)",
"type": "xiaoxiang_error",
"code": "insufficient_user_quota"
}
}Erro do upstream, repassado
O provedor respondeu com o próprio erro. O corpo é encaminhado sem alterações, então type e code são os do provedor.
{
"error": {
"message": "This model's maximum context length is 128000 tokens.",
"type": "invalid_request_error",
"code": "context_length_exceeded"
}
}Rejeição por verificação prévia
A idempotência e algumas outras verificações anteriores ao relay respondem antes de o envelope do relay existir. Formato plano, code em maiúsculas.
{
"code": "IDEMPOTENCY_IN_PROGRESS",
"error": "a request with this Idempotency-Key is still processing"
}Rejeição de tarefa assíncrona
Todo endpoint de tarefa — vídeo, tarefa de imagem, música, 3D — informa falhas no próprio formato plano. Não há objeto error aninhado aqui, então um parser escrito para /v1/chat/completions lê null.
{
"code": "task_not_exist",
"message": "task does not exist",
"data": null
}Só status, corpo vazio
Rejeições por limite de taxa e corpos de requisição grandes demais são interrompidos apenas com o status.
HTTP/1.1 429 Too Many Requests
Retry-After: 20
Content-Length: 0Cabeçalhos de resposta
Registre o ID da requisição em toda resposta, não só nas falhas. É o identificador pelo qual o suporte rastreia uma chamada, e é a diferença entre uma resposta em cinco minutos e um dia de suposições.
| Cabeçalho | Enviado quando | O que fazer com ele |
|---|---|---|
| X-Oneapi-Request-Id | Toda resposta, de sucesso ou de erro. | Registre-o junto ao seu próprio trace id. O suporte responde aos chamados com base neste valor, e o mesmo id é anexado às mensagens de erro do gateway. |
| Retry-After | Em 429, e em 409 enquanto uma Idempotency-Key ainda está em processamento. | Aguarde pelo menos esse número de segundos. É a única indicação de backoff que o gateway envia. |
| X-Idempotent-Replayed | Uma resposta armazenada é reproduzida para uma Idempotency-Key repetida. | true significa que nenhum trabalho novo foi executado e nada foi cobrado de novo — trate o corpo como o resultado original. |
| X-Idempotency-Sealed | A resposta original não pode ser reproduzida com segurança (por exemplo, um stream ou um corpo grande demais). | Pare de tentar com essa chave. Em vez disso, faça a conciliação pelo endpoint da tarefa ou pelos seus próprios registros. |
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);
}#Tabela de códigos de erro
Erros gerados pelo próprio gateway, sempre com type "xiaoxiang_error". Duas linhas compartilham o status 429 e significam coisas opostas — leia o code antes de fazer backoff.
| HTTP | Código | Significado | O que fazer | Repetir? |
|---|---|---|---|---|
| 401 | nenhum | Bearer token ausente, malformado, excluído ou desativado. Esta camada não define code — verifique pelo status. | Verifique o cabeçalho Authorization e o estado da chave em Console → Chaves de API. | não |
| 401 | signature_required | A chave tem assinatura de requisições ativada e a requisição não tem X-Timestamp, X-Nonce ou X-Signature. | Assine todas as requisições feitas com esta chave (Autenticação e chaves → Assinatura de requisições) ou desative a assinatura na chave. | não |
| 401 | signature_expired | X-Timestamp não está em segundos Unix de 10 dígitos ou difere em mais de 5 minutos do horário do servidor. A resposta traz X-Server-Timestamp. | Corrija o relógio do cliente pela diferença de X-Server-Timestamp e assine de novo com um novo nonce. | sim |
| 401 | signature_invalid | X-Signature não corresponde ao segredo de assinatura da chave, ou X-Nonce está malformado. | Assine timestamp, nonce, método e caminho (sem query string), unidos por quebras de linha, com o segredo atual de Console → Chaves de API. | não |
| 401 | signature_replayed | Este X-Nonce já foi usado com esta chave nos últimos 10 minutos. | Gere um nonce aleatório novo para cada requisição, inclusive nas novas tentativas. | sim |
| 403 | access_denied | O IP de origem está fora da lista de IPs permitidos da chave. | Adicione o endereço de saída em Console → Chaves de API ou remova a lista de permissões das chaves sem IP fixo. | não |
| 403 | nenhum | Conta suspensa, ou a chave não tem permissão para usar este modelo ou canal. | Verifique primeiro a lista de modelos permitidos da chave; se ela não tiver restrições, abra um chamado — a própria conta está bloqueada. | não |
| 400 | unsupported_endpoint | O modelo existe, mas não atende a este caminho. A mensagem lista os endpoints que ele atende. | Mova a chamada para um endpoint da lista — modelos de tarefa de imagem não respondem em /v1/chat/completions. | não |
| 400 | read_request_body_failed | Não foi possível ler o corpo da requisição — upload truncado, Content-Type errado ou um stream que terminou antes da hora. | Envie um corpo completo com o Content-Type correspondente. Reenviar a mesma requisição quebrada falha do mesmo jeito. | não |
| 402 | insufficient_user_quota | O saldo da carteira não cobre o valor pré-reservado para esta chamada. | Recarregue em Console → Carteira e tente novamente. | não |
| 429 | insufficient_user_quota | O limite de gastos diário da conta foi atingido. Mesmo status da limitação de taxa, significado oposto — backoff não resolve. | Aumente ou remova o limite nas configurações da conta, ou retome amanhã. Decida pelo code, não apenas pelo 429. | não |
| 429 | nenhum | Limite de RPM/RPD por chave ou proteção de concorrência. O corpo vem vazio; Retry-After informa a espera. | Aguarde Retry-After segundos e tente novamente com backoff exponencial e jitter. | sim |
| 413 | read_request_body_failed | O corpo da requisição excede o tamanho aceito. O limite da camada de transporte interrompe com corpo vazio. | Reduza o payload — envie arquivos por /v1/media/upload-policy e mande a URL em vez de Base64. | não |
| 422 | invalid_request | Nenhum canal oferece essa intenção de mídia para o modelo — por exemplo, imagem para vídeo em uma rota só de texto para vídeo. | Confira na ficha do modelo as intenções suportadas e troque de modelo ou de intenção. | não |
| 503 | model_not_found | Nome de modelo desconhecido, ou nenhum canal saudável o atende no momento. Atenção ao status: um modelo desconhecido retorna 503, não 404. | Confira o nome em /v1/models e tente novamente — o failover geralmente encontra um canal saudável na próxima tentativa. | sim |
| 500 | sensitive_words_detected | O prompt foi barrado pelo filtro de conteúdo antes de sair do gateway. | Altere o prompt. Tentar de novo com o mesmo texto sempre falha. | não |
| 500 | count_token_failed | O gateway não conseguiu estimar os tokens para a precificação, geralmente por causa de um array de mensagens malformado. | Valide o array messages; se estiver bem formado, tente novamente e abra um chamado com o ID da requisição. | sim |
| 500 | invalid_request | Não foi possível interpretar a requisição em um formato aceito por este endpoint. Atenção ao status — um corpo que o gateway não consegue ler é reportado como 500, não 400. | Valide o JSON com a referência do endpoint antes de tentar novamente. | não |
| 502 | do_request_failed | O provedor estava inacessível ou retornou uma falha que o gateway não conseguiu contornar com failover. | Tente novamente com backoff — a próxima tentativa geralmente cai em outro canal. | sim |
| 504 | do_request_failed | O provedor não respondeu dentro do prazo do gateway. | Tente novamente com backoff; prompts longos e provedores sobrecarregados estouram o tempo limite com mais frequência. | sim |
| 499 | do_request_failed | Seu cliente fechou a conexão antes de a resposta chegar — geralmente o próprio timeout HTTP disparando no meio do stream. | Aumente o timeout de leitura do cliente. Gerações longas levam minutos, não o padrão da maioria das bibliotecas HTTP. | não |
Erros da proteção de idempotência
Estes vêm da proteção contra repetição que fica antes do relay, por isso usam o formato plano {code, error} com code em maiúsculas em vez do envelope da OpenAI.
| HTTP | Código | Significado | O que fazer | Repetir? |
|---|---|---|---|---|
| 400 | IDEMPOTENCY_STREAM_UNSUPPORTED | Idempotency-Key foi enviada em uma requisição de streaming. | Remova o cabeçalho nas chamadas de streaming ou defina stream:false quando precisar de proteção contra repetição. | não |
| 400 | IDEMPOTENCY_BODY_UNREADABLE | Não foi possível gerar o hash do corpo para o registro de idempotência. | Envie um corpo JSON ou multipart normal — corpos chunked que não podem ser armazenados em buffer são rejeitados. | não |
| 409 | IDEMPOTENCY_PAYLOAD_CONFLICT | Esta chave já foi usada com outro corpo, caminho ou query. | Gere uma nova chave. Uma chave identifica uma requisição exata, não um ciclo de novas tentativas. | não |
| 409 | IDEMPOTENCY_IN_PROGRESS | A primeira requisição com esta chave ainda está em execução. | Aguarde o Retry-After (2s) e tente novamente com a mesma chave — você receberá a resposta original. | sim |
| 409 | IDEMPOTENCY_RESULT_UNREPLAYABLE | A requisição original terminou, mas a resposta foi selada e não pode ser reproduzida. | Não reenvie às cegas. Faça primeiro a conciliação pelo endpoint da tarefa ou pelos seus próprios registros. | não |
| 503 | IDEMPOTENCY_UNAVAILABLE | O registro de idempotência está indisponível, então a proteção falha fechada em vez de arriscar uma cobrança dupla. | Tente novamente com backoff — a requisição não foi executada. | sim |
Erros de tarefas assíncronas
Os endpoints de vídeo, tarefa de imagem, música e 3D respondem com o próprio formato plano — {code, message, data} — tanto no envio quanto na consulta e no cancelamento. Uma peculiaridade a considerar: consultar um id de tarefa desconhecido retorna 400; cancelar, 404.
| HTTP | Código | Significado | O que fazer | Repetir? |
|---|---|---|---|---|
| 400 | task_not_exist | Consultar ou fazer remix de um id de tarefa que não existe ou pertence a outra conta. Atenção ao status — consultar uma tarefa desconhecida retorna 400, enquanto cancelar uma retorna 404. | Persista o task_id no momento do envio e consulte com exatamente esse valor. | não |
| 400 | invalid_request | Falta um campo obrigatório — prompt em um remix, task_id em uma consulta. | Corrija o corpo ou o caminho antes de tentar novamente. | não |
| 400 | task_channel_disable | Uma tarefa derivada (remix, extensão) fica vinculada ao canal da tarefa original, e esse canal agora está desativado. | Envie uma tarefa nova em vez de derivar da antiga. | não |
| 400 | invalid_source_task | A tarefa de origem é do tipo errado para esta operação — por exemplo, um id de vídeo passado para uma continuação 3D. | Verifique se o id de origem veio da mesma família de recursos. | não |
| 404 | task_not_exist | Cancelamento de um id de tarefa que não existe ou não é seu. | Não há nada para cancelar — considere que já não existe. | não |
| 409 | task_not_cancellable | A tarefa já chegou a um estado final, então não há nada para cancelar. | Consulte mais uma vez e use o resultado pelo qual você já pagou. | não |
| 501 | task_cancel_not_supported | O provedor por trás desta tarefa não oferece API de cancelamento. A maioria não oferece — cancelar é a exceção, não a regra. | Deixe a tarefa terminar. Projete a UI para que o cancelamento seja de melhor esforço, não uma garantia. | não |
| 502 | cancel_task_failed | O provedor estava inacessível durante o cancelamento. A tarefa não foi alterada. | Tente mais uma vez e, se falhar, deixe a tarefa terminar. | sim |
Ainda com problemas?
Envie para nós o ID da requisição ou a mensagem de erro e vamos investigar.