Tratamento de erros
Uma requisição que falha retorna um código de status HTTP e um corpo JSON com um tipo de erro e uma mensagem. Esta página lista todos os status, mostra o corpo de erro de cada formato de API e diz quais erros vale a pena enviar de novo.
Os SDKs levantam um objeto de erro que traz o código de status e os campos do corpo. Esta requisição indica um modelo que não existe:
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"}]
}' Ela retorna o status 400 e este corpo:
{
"error": {
"type": "invalid_request_error",
"message": "unknown model: no-such-model"
}
} Corpo do erro
Há dois corpos. /v1/messages responde no formato Anthropic; todos os outros endpoints respondem no formato OpenAI. Nos dois, os detalhes estão em error.type e error.message, então um único leitor serve a todos os endpoints.
{
"error": {
"type": "authentication_error",
"message": "Invalid API key",
"code": "invalid_api_key",
"param": null
}
} {
"type": "error",
"error": {
"type": "authentication_error",
"message": "Invalid API key"
}
} | Campo | Tipo | Descrição |
|---|---|---|
error.type | string | O tipo de erro. Veja Tipos de erro abaixo. |
error.message | string | Uma frase para uma pessoa. Os textos nas tabelas abaixo são estáveis, exceto nas partes variáveis, mostradas como <id>, <n> ou …. |
error.code | string | null | Presente em alguns erros do formato OpenAI: invalid_api_key com 401, request_too_large com 413, context_length_exceeded quando uma conversa é longa demais; nos demais casos, null ou ausente. Decida primeiro pelo status e por error.type e trate code como um extra. |
error.param | null | Presente nos mesmos erros que code. Sempre null. |
type | string | Somente no formato Anthropic. Sempre error. |
Tipos de erro
| Tipo | Status | Descrição |
|---|---|---|
invalid_request_error | 400, 413, 415, 422 | A própria requisição está errada: um campo, o corpo, o id do modelo ou uma entrada que o modelo não aceita. Altere a requisição antes de enviá-la de novo. |
authentication_error | 401 | A chave de API está ausente, é desconhecida ou foi revogada. |
not_found_error | 404 | O caminho não existe. |
rate_limit_error | 429 | O saldo não cobre a requisição, a proteção contra flood a bloqueou, ou o modelo está ocupado. A mensagem diz qual. |
api_error | 500, 502, 503, 504 | A requisição estava correta e a falha é do nosso lado. Envie-a de novo. |
server_error | 500 | A requisição estava correta e a falha é do nosso lado. Envie-a de novo. |
Códigos de status
| Status | Tipo | Mensagem | Quando |
|---|---|---|---|
400 | invalid_request_error | Failed to parse the request body as JSON: … | O corpo não é um JSON válido. |
400 | invalid_request_error | unknown model: <id> | model não é um id publicado. Em /v1/responses, também quando model está ausente. |
400 | invalid_request_error | No user message provided | Níveis Shannon: a requisição não contém texto de usuário. |
400 | invalid_request_error | <id> does not accept image input | Uma imagem foi enviada a um modelo open-weight hospedado sem entrada de imagem. |
400 | invalid_request_error | audio and video input are not supported by any hosted model | Uma parte de áudio ou vídeo foi enviada a um modelo open-weight hospedado. |
400 | invalid_request_error | <id> does not accept response_format | Saída estruturada foi pedida a um modelo open-weight hospedado que não a tem. |
400 | invalid_request_error | <id> does not accept response_format json_schema; use json_object | Um JSON schema foi enviado a um modelo open-weight hospedado que aceita apenas json_object. |
400 | invalid_request_error | unknown reasoning effort '<value>'; expected off, low, medium or high | O esforço de raciocínio não é um dos valores listados. Modelos open-weight hospedados. |
400 | invalid_request_error | count_tokens is available for the hosted open models; unknown model: <id> | A contagem de tokens foi pedida para um modelo fora dos modelos open-weight hospedados. |
401 | authentication_error | Missing authentication | Nenhuma chave de API foi enviada. |
401 | authentication_error | Invalid API key | A chave é desconhecida, foi substituída por uma mais nova ou foi revogada. |
404 | not_found_error | Not Found | O caminho não existe, por exemplo /v1/embeddings. |
405 | api_error | Method Not Allowed | O caminho existe, mas não com este método HTTP. |
413 | invalid_request_error | Failed to buffer the request body: … | O corpo da requisição é maior que 32 MiB. |
415 | invalid_request_error | Expected request with `Content-Type: application/json` | O header Content-Type está ausente ou não é application/json. |
422 | invalid_request_error | Failed to deserialize the JSON body into the target type: … | Falta um campo obrigatório, como messages, ou um campo tem o tipo JSON errado. |
429 | rate_limit_error | Quota exceeded. Upgrade your plan at shannon-ai.com/plan | O limite de saída da requisição é maior do que o que resta do seu saldo. |
429 | rate_limit_error | Too many requests. Retry in <n>s. | Proteção contra flood: mais de 120 requisições em um minuto na sua conta. <n> é o número de segundos até o fim do minuto. |
429 | rate_limit_error | Shannon Coder call quota reached. Upgrade your plan at shannon-ai.com/plan | shannon-coder-1 em /v1/chat/completions e /v1/messages: as chamadas do Shannon Coder do seu plano para a janela atual de 4 horas foram esgotadas. |
429 | rate_limit_error | Shannon routes are temporarily busy. Please retry. | O modelo está ocupado neste momento. |
500 | server_error | The model backend failed to answer. Please retry. | O modelo não produziu uma resposta. |
500 | server_error | An internal error occurred. Please retry. | Uma falha interna enquanto a requisição era preparada. |
502 | api_error | The model backend failed to answer. Please retry. | O modelo não produziu uma resposta. Família Shannon 3 e modelos open-weight hospedados. |
503 | api_error | Could not verify your quota right now. Please retry. | Seu saldo não pôde ser lido neste momento. |
503 | api_error | token counting is temporarily unavailable for this model | A contagem de tokens não está disponível para este modelo neste momento. |
504 | api_error | upstream timed out | Um modelo open-weight hospedado demorou mais que o limite de tempo de uma requisição sem streaming. |
As verificações são feitas nesta ordem: chave de API, corpo da requisição, id do modelo, proteção contra flood, saldo. Uma requisição com chave errada e corpo errado, portanto, retorna 401. Uma requisição recusada com um status 4xx não é cobrada.
Saldo e proteção contra flood
Todas as requisições são atendidas igualmente: não há faixas de limite de taxa nem cota de API separada. Mesmo assim, duas coisas podem responder 429, ambas com o tipo rate_limit_error. A mensagem as distingue.
Saldo
Seu saldo é a cota do plano de hoje, que é renovada às 00:00 UTC, e depois dela os seus créditos comprados. O chat e a API usam o mesmo saldo.
Cada requisição reserva primeiro o seu limite de saída: max_tokens, ou max_output_tokens em /v1/responses, 4,096 quando você não envia nenhum dos dois. Quando a resposta termina, a quantia reservada é substituída pelo que a requisição usou. Se a quantia não couber no saldo restante, a requisição retorna Quota exceeded antes de o modelo começar.
Requisições simultâneas reservam, cada uma, a sua própria quantia. Muitas requisições em paralelo com um max_tokens alto podem, portanto, esgotar o saldo por um momento, mesmo que cada resposta seja curta. Um max_tokens menor reserva menos.
Proteção contra flood
A proteção contra flood permite 120 requisições por minuto por conta. Ela conta a conta, não a chave, em /v1/chat/completions, /v1/responses e /v1/messages juntos. O minuto começa com a sua primeira requisição.
A requisição de número 121 dentro desse minuto retorna Too many requests. Retry in <n>s. e não custa nada. Requisições recusadas também contam, então um cliente que reenvia sem esperar continua recusado até o fim do minuto. Espere os <n> segundos indicados na mensagem.
Requisições enviadas em paralelo não são recusadas por serem paralelas. Elas esperam em fila e são respondidas em ordem.
GET /v1/models e os endpoints de contagem de tokens não são contados.
Mais sobre limites e saldo, e onde ver seu próprio saldo e uso: Limites e saldo Chaves e uso
O que tentar de novo
Envie uma requisição de novo somente quando a mesma requisição puder ter sucesso. Os SDKs da OpenAI e da Anthropic já repetem por conta própria duas vezes as respostas 429 e 5xx; defina max_retries (maxRetries em JavaScript) para mudar isso.
| Erro | Enviar de novo? | Como |
|---|---|---|
400, 404, 405, 413, 415, 422 | Não. Corrija a requisição primeiro. | A mensagem indica o campo, o modelo ou a entrada que está errada. |
401 | Não. Use uma chave válida. | Crie ou substitua a chave na página Chaves e uso. |
429 Too many requests | Sim, depois da espera. | Espere o número de segundos indicado na mensagem e envie a mesma requisição. |
429 Quota exceeded | Não como está. | Reduza max_tokens, aguarde a renovação diária às 00:00 UTC ou adicione créditos. |
429 Shannon Coder call quota reached | Sim, na próxima janela. | As janelas começam a cada 4 horas a partir das 00:00 UTC. Outro modelo responde imediatamente. |
429 … temporarily busy | Sim. | Espere alguns segundos e, a cada nova tentativa, espere mais. |
500, 502, 503 | Sim. | Espere 1 segundo, depois 2, 4 e 8. Pare após algumas tentativas e informe o id da requisição. |
504 | Sim. | Envie-a com stream definido como true: o limite de tempo vale para respostas sem streaming. |
Um loop de repetição que segue esta tabela:
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); Erros dentro de um stream
Um stream tem status 200 desde o primeiro byte. Uma falha depois disso chega como um evento dentro do stream: um chunk com um membro error em /v1/chat/completions, um evento error em /v1/messages, um evento response.failed ou error em /v1/responses. Os eventos finais do stream ainda vêm em seguida. Trate essa resposta como falha e envie a requisição de novo, como para um status 5xx.
Uma conversa mais longa que a janela de contexto do modelo é informada desta forma, com o tipo invalid_request_error e o código context_length_exceeded. Reduza a conversa antes de enviá-la de novo.
Os frames exatos são descritos junto com o streaming. Streaming
Ids de requisição
Toda resposta, inclusive as de erro, tem um header x-request-id. É o valor que você enviou no seu próprio header x-request-id, ou 12 caracteres hexadecimais quando você não enviou nenhum. Registre-o em log e cite-o ao pedir ajuda com uma requisição.