Skip to content
Error Handling

Error Handling

A request that fails returns an HTTP status code and a JSON body with an error type and a message. This page lists every status, shows the error body of each API format and says which errors are worth sending again.

The SDKs raise an error object that carries the status code and the fields of the body. This request names a model that does not exist:

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

It returns status 400 and this body:

400 JSON
{
  "error": {
    "type": "invalid_request_error",
    "message": "unknown model: no-such-model"
  }
}

Error body

There are two bodies. /v1/messages answers in the Anthropic format; every other endpoint answers in the OpenAI format. In both, the details are in error.type and error.message, so one reader serves all endpoints.

401 OpenAI format
{
  "error": {
    "type": "authentication_error",
    "message": "Invalid API key",
    "code": "invalid_api_key",
    "param": null
  }
}
401 Anthropic format
{
  "type": "error",
  "error": {
    "type": "authentication_error",
    "message": "Invalid API key"
  }
}
Field Type Description
error.type string The kind of error. See Error types below.
error.message string A sentence for a person. The texts in the tables below are stable up to their variable parts, shown as <id>, <n> or ….
error.code string | null Present on some errors of the OpenAI format: invalid_api_key with 401, request_too_large with 413, context_length_exceeded when a conversation is too long, otherwise null or absent. Decide on the status and error.type first and treat code as an extra.
error.param null Present on the same errors as code. Always null.
type string Anthropic format only. Always error.

Error types

Type Statuses Description
invalid_request_error 400, 413, 415, 422 The request itself is wrong: a field, the body, the model id or an input the model does not take. Change the request before you send it again.
authentication_error 401 The API key is missing, unknown or revoked.
not_found_error 404 The path does not exist.
rate_limit_error 429 The balance does not cover the request, flood protection stopped it, or the model is busy. The message says which.
api_error 500, 502, 503, 504 The request was fine and the failure is on our side. Send it again.
server_error 500 The request was fine and the failure is on our side. Send it again.

Status codes

Status Type Message When
400 invalid_request_error Failed to parse the request body as JSON: … The body is not valid JSON.
400 invalid_request_error unknown model: <id> model is not a published id. On /v1/responses also when model is missing.
400 invalid_request_error No user message provided Shannon tiers: the request holds no user text.
400 invalid_request_error <id> does not accept image input An image was sent to a hosted open-weight model without image input.
400 invalid_request_error audio and video input are not supported by any hosted model An audio or video part was sent to a hosted open-weight model.
400 invalid_request_error <id> does not accept response_format Structured output was asked from a hosted open-weight model without it.
400 invalid_request_error <id> does not accept response_format json_schema; use json_object A JSON schema was sent to a hosted open-weight model that takes json_object only.
400 invalid_request_error unknown reasoning effort '<value>'; expected off, low, medium or high The reasoning effort is not one of the listed values. Hosted open-weight models.
400 invalid_request_error count_tokens is available for the hosted open models; unknown model: <id> Token counting was asked for a model outside the hosted open-weight models.
401 authentication_error Missing authentication No API key was sent.
401 authentication_error Invalid API key The key is unknown, was replaced by a newer one, or was revoked.
404 not_found_error Not Found The path does not exist, for example /v1/embeddings.
405 api_error Method Not Allowed The path exists but not with this HTTP method.
413 invalid_request_error Failed to buffer the request body: … The request body is larger than 32 MiB.
415 invalid_request_error Expected request with `Content-Type: application/json` The Content-Type header is missing or is not application/json.
422 invalid_request_error Failed to deserialize the JSON body into the target type: … A required field is missing, such as messages, or a field has the wrong JSON type.
429 rate_limit_error Quota exceeded. Upgrade your plan at shannon-ai.com/plan The request's output limit is larger than what is left of your balance.
429 rate_limit_error Too many requests. Retry in <n>s. Flood protection: more than 120 requests in one minute on your account. <n> is the number of seconds until the minute ends.
429 rate_limit_error Shannon Coder call quota reached. Upgrade your plan at shannon-ai.com/plan shannon-coder-1 on /v1/chat/completions and /v1/messages: your plan's Shannon Coder calls for the current 4-hour window are used.
429 rate_limit_error Shannon routes are temporarily busy. Please retry. The model is busy at this moment.
500 server_error The model backend failed to answer. Please retry. The model did not produce a reply.
500 server_error An internal error occurred. Please retry. An internal failure while the request was prepared.
502 api_error The model backend failed to answer. Please retry. The model did not produce a reply. Shannon 3 family and hosted open-weight models.
503 api_error Could not verify your quota right now. Please retry. Your balance could not be read at this moment.
503 api_error token counting is temporarily unavailable for this model Token counting is not available for this model at this moment.
504 api_error upstream timed out A hosted open-weight model took longer than the time limit of a request that is not streamed.

Checks run in this order: API key, request body, model id, flood protection, balance. A request with a wrong key and a wrong body therefore returns 401. A request that is refused with a 4xx status is not charged.

Balance and flood protection

Every request is served equally: there are no rate tiers and no separate API quota. Two things can still answer 429, both with the type rate_limit_error. The message tells them apart.

Balance

Your balance is today's plan allowance, which resets at 00:00 UTC, and after it your purchased credit. Chat and the API draw from the same balance.

Each request first sets aside its output limit: max_tokens, or max_output_tokens on /v1/responses, 4,096 when you send neither. When the reply is complete, the amount set aside is replaced by what the request used. If the amount does not fit into the balance that is left, the request returns Quota exceeded before the model starts.

Requests that run at the same time each set aside their own amount. Many parallel requests with a high max_tokens can therefore use up the balance for a moment, even when each reply is short. A lower max_tokens sets aside less.

Flood protection

Flood protection allows 120 requests per minute per account. It counts the account, not the key, over /v1/chat/completions, /v1/responses and /v1/messages together. The minute starts with your first request.

Request number 121 inside that minute returns Too many requests. Retry in <n>s. and costs nothing. Refused requests count as well, so a client that sends again without waiting stays refused until the minute is over. Wait the <n> seconds from the message.

Requests sent in parallel are not refused for being parallel. They wait in line and are answered in turn.

GET /v1/models and the token counting endpoints are not counted.

More on limits and balance, and where to see your own balance and usage: Limits and balance Keys & usage

What to retry

Send a request again only when the same request can succeed. The OpenAI and Anthropic SDKs already retry 429 and 5xx replies twice on their own; set max_retries (maxRetries in JavaScript) to change that.

Error Send again? How
400, 404, 405, 413, 415, 422 No. Correct the request first. The message names the field, the model or the input that is wrong.
401 No. Use a valid key. Create or replace the key on the Keys & usage page.
429 Too many requests Yes, after the wait. Wait the number of seconds in the message, then send the same request.
429 Quota exceeded Not as it is. Lower max_tokens, wait for the daily reset at 00:00 UTC, or add credit.
429 Shannon Coder call quota reached Yes, in the next window. The windows start every 4 hours from 00:00 UTC. Another model answers at once.
429 … temporarily busy Yes. Wait a few seconds, then longer on each further attempt.
500, 502, 503 Yes. Wait 1 second, then 2, 4 and 8. Stop after a few attempts and report the request id.
504 Yes. Send it with stream set to true: the time limit applies to replies that are not streamed.

A retry loop that follows this table:

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)

Errors inside a stream

A stream has status 200 from its first byte. A failure after that arrives as an event inside the stream: a chunk with an error member on /v1/chat/completions, an error event on /v1/messages, a response.failed or error event on /v1/responses. The closing events of the stream still follow. Treat such a reply as failed and send the request again, as for a 5xx status.

A conversation that is longer than the model's context window is reported this way, with the type invalid_request_error and the code context_length_exceeded. Shorten the conversation before you send it again.

The exact frames are described with streaming. Streaming

Request ids

Every reply, errors included, has an x-request-id header. It is the value you sent in your own x-request-id header, or 12 hexadecimal characters when you sent none. Log it, and quote it when you ask for help with a request.