Optimize
Errors
What each HTTP status code and error message from the API means, how to fix 401, 403, 404 and 429 errors, low balance and unknown models, and when to retry.
On this page
Errors come back as normal HTTP status codes with a JSON body that says what went wrong. Most problems are one of three things: the key, the base URL, or the balance. This page helps you tell them apart quickly.
Error format#
Errors use the OpenAI-style error envelope, which every OpenAI SDK already understands. Errors that come from the model provider keep the provider's status code and message.
{
"error": {
"code": "",
"message": "Invalid token (request id: 20261008101503123456789abcdef)",
"type": "api_error"
}
}{
"error": {
"code": "insufficient_user_quota",
"message": "Your balance is too low for this request. Add credit under Billing in the console. (request id: 20261008101503123456789abcdef)",
"type": "api_error"
}
}{
"error": {
"code": "model_not_found",
"message": "No available channel for model claude-sonnet-5-5 under group default (distributor) (request id: 20261008101503123456789abcdef)",
"type": "api_error"
}
}{
"error": {
"message": "Unknown endpoint. The API lives under /v1 (for example POST /v1/chat/completions).",
"type": "invalid_request_error",
"param": null,
"code": "not_found"
}
}Branch on the HTTP status first, then on error.code where it is set (for example model_not_found or insufficient_user_quota). Match on the code, not on the message text: messages are written for people and can change. Log error.message anyway, since it names the model, group or limit involved.
On /v1/messages#
Once a request on the Anthropic endpoint reaches the model step, errors use Anthropic's shape. Checks that run before that (key, model allowlist, IP allowlist, model availability) answer with the OpenAI envelope above on every endpoint. The Anthropic SDKs raise an error from the HTTP status either way.
{
"type": "error",
"error": {
"type": "api_error",
"message": "Your balance is too low for this request. Add credit under Billing in the console. (request id: 20261008101503123456789abcdef)"
}
}Request id#
Every response has an X-Request-Id header, and gateway error messages end with (request id: …). Log it with failures; it is the fastest way for support to find your request.
Status codes#
Fix the request, do not retry Safe to retry with backoff
- 400Bad RequestBad RequestFix, do not retry
Cause: Invalid JSON, a missing or invalid field, a prompt longer than the context window, or a model that is listed but not priced yet (model_price_error).
Fix: Read the message, fix the body, shorten the input. Details
- 401UnauthorizedUnauthorizedFix, do not retry
Cause: Key missing, mistyped, disabled, expired or deleted, or sent in a header the endpoint does not read.
Fix: Check the header and that the environment variable is loaded. Create a new key if needed. Details
- 403ForbiddenForbiddenFix, do not retry
Cause: Balance, plan allowance or the key spending limit is used up; the key may not use this model or group; or the request came from an IP the key does not allow.
Fix: Add credit, raise the key limit, or edit the key settings. Details
- 404Not FoundNot FoundFix, do not retry
Cause: Wrong URL path, usually /v1 missing (OpenAI format) or doubled (Anthropic format).
Fix: Use the base URLs exactly as shown in the docs. Details
- 413Payload Too LargePayload Too LargeFix, do not retry
Cause: The request body is over 128 MB, usually from large base64 images or files.
Fix: Shrink or split the input. Details
- 429Too Many RequestsToo Many RequestsRetry with backoff
Cause: The model provider is throttling, or a per-model rate limit is in force.
Fix: Back off and retry. Reduce parallel requests. Details
- 500Internal Server ErrorInternal Server ErrorRetry with backoff
Cause: Unexpected error in the gateway or at the provider.
Fix: Retry with backoff. Report it with the request id if it persists. Details
- 501Not ImplementedNot ImplementedFix, do not retry
Cause: An OpenAI endpoint the gateway does not offer, such as Files, fine-tuning or image variations.
Fix: Use a supported endpoint (see the API reference). Details
- 502Bad GatewayBad GatewayRetry with backoff
Cause: The provider returned an invalid response or dropped the connection.
Fix: Retry with backoff. Details
- 503Service UnavailableService UnavailableRetry with backoff
Cause: No provider route is available for the model right now, the model is not enabled for your account, or the provider is overloaded.
Fix: Check the model id; retry later or switch to a sibling model. Details
Invalid or missing key#
Every key problem returns 401 with the message Invalid token, whatever the exact cause, so check each of these:
- Print the variable in the same terminal (
echo $RELAYKIT_API_KEY, or$env:RELAYKIT_API_KEYin PowerShell). An empty value is the most common cause. - Make sure the value starts with
sk-and has no quotes, spaces or line breaks copied along with it. - OpenAI clients send
Authorization: Bearer; Anthropic clients sendx-api-key.x-api-keyis read only on/v1/messagesand/v1/models; everywhere else use the Bearer header. - Check under API keys that the key is enabled, not past its expiry date, and not deleted.
Insufficient balance#
When the request cannot be paid for, the gateway rejects it before calling the model, with 403. Nothing is charged. The code tells you which limit was hit:
insufficient_user_quota: the account balance (or the plan allowance, if billing uses a plan only) is too low. Add credit under Billing.pre_consume_token_quota_failed: this key's own spending limit is reached, even though the account may have money. Raise or remove the limit under API keys.
Before a request runs, the gateway reserves an estimate of its cost, so a request can be refused while the balance is still slightly above zero. A lower max_tokens lowers the reservation. New balance applies to the next request immediately.
Access denied (other 403s)#
All 403 messages you can get from the gateway, and what each one means:
| Message | code | Meaning |
|---|---|---|
| Your balance is too low for this request. Add credit under Billing in the console. | insufficient_user_quota | Your account balance cannot cover the request. |
| Your plan allowance is used up or no plan is active. Add balance, or wait for the allowance to reset. | insufficient_user_quota | Billing is set to use a plan only, and the plan allowance is spent. |
| token quota is not enough, token remain quota: …, need quota: … | pre_consume_token_quota_failed | This key reached the spending limit you set on it. The account balance is not the problem. |
| This token has no access to model … | (empty) | The key has a model allowlist and this model is not on it. |
| This key or account cannot use the … group | (empty) | The key is pinned to a group your account cannot use, often after a plan change. |
| The … group is no longer available | (empty) | The key is pinned to a group that was retired. |
| Your IP address is not on this key's allowed list | access_denied | The key has an IP allowlist and the request came from another address. Check the key’s Allowed IP addresses on the keys page, or clear the field to accept any address. |
Spend limit, expiry and model list are on the key's edit dialog under API keys. See Authentication and keys.
Model not found#
If the gateway cannot route the model you sent, it answers 503 with code model_not_found and a message that names the model, or with "Model … is not available right now. Try again shortly or pick another model." Some clients show this as "model not found" or 404.
- Copy ids exactly from
GET /v1/modelsor the models table. Ids are case-sensitive:claude-sonnet-5-5, not a display name. - New accounts start at $0 and see no models until the account has paid $3 or more in total; until then every model returns this error. Add credit under Billing.
- Calling an OpenAI-only model on
/v1/messages? Use the OpenAI endpoint instead (see models by format).
Bad request and context length#
A 400 means the request itself needs changing, so retrying it unchanged will fail again. Typical messages mention an invalid parameter, a missing model, an image the model cannot read, or a prompt that exceeds the context window. For long conversations, trim or summarise old turns, and keep max_tokens within the model's output limit.
model_price_error("Model … has not been priced by the administrator yet"): the model is listed but not open for use yet. Pick another model, or tell us which one you need.413: the body is larger than 128 MB, almost always because of inline images or files. Resize them or send fewer per request.
Not implemented (501)#
Some OpenAI endpoints are not offered: Files (/v1/files), fine-tuning, image variations, deleting models, and /v1/messages/count_tokens. They return 501 with code api_not_implemented (or 404). The API reference lists what is available.
Rate limited (429)#
429 means slow down, not stop. Today it almost always comes from the model provider during peak load; the gateway does not apply its own per-account request limit on the API. If a per-model limit is switched on, its message says so: "Rate limit reached: at most N requests per M minutes". Retry with exponential backoff, honour a Retry-After header when present, and cap how many requests you run in parallel. See Rate limits and retries.
Upstream errors (5xx)#
500, 502 and 503 are usually short-lived: a provider hiccup or a capacity spike for one model. Retry with backoff. The gateway does not retry for you, so a failed attempt reaches your code as it happened. If one model keeps failing, switch to a sibling model temporarily; the rest of your code does not change because every model shares the same API.
Timeouts#
- Streaming: the gateway closes a stream when the provider sends nothing for 300 seconds. Reasoning models with long thinking phases are the usual cause.
- Non-streaming: the gateway does not cut the request off, but your HTTP client and any proxy on the way may. A long answer with nothing sent until the end is the most likely to time out.
- Use
stream: truefor long outputs: tokens flow continuously, so nothing sits idle. Set your client's read timeout to a few minutes. - Do not blindly retry a timed-out request: the model may have finished and been billed even though your client never saw the answer. If you disconnect early, you pay for the tokens produced up to that point.
Retries with backoff#
Retry only on 429, 500, 502, 503 and network errors. Wait longer after each attempt (1s, 2s, 4s… with random jitter) and give up after about five tries. Never retry 400, 401, 403, 404 or 501: they will fail the same way.
import os, random, time
from openai import OpenAI, APIStatusError, APIConnectionError
client = OpenAI(
base_url="https://api.oneaiapikey.com/v1",
api_key=os.environ["RELAYKIT_API_KEY"],
max_retries=0, # we handle retries ourselves below
)
RETRYABLE = {429, 500, 502, 503}
def backoff(attempt, retry_after=None):
if retry_after and retry_after.isdigit():
return int(retry_after) # honour the server's hint
return min(30, 2 ** attempt) + random.random() # 1s, 2s, 4s… + jitter
def chat(attempts=5, **kwargs):
for attempt in range(attempts):
try:
return client.chat.completions.create(**kwargs)
except APIStatusError as e:
if e.status_code not in RETRYABLE or attempt == attempts - 1:
raise
time.sleep(backoff(attempt, e.response.headers.get("retry-after")))
except APIConnectionError:
if attempt == attempts - 1:
raise
time.sleep(backoff(attempt))
reply = chat(model="gpt-5.6-terra", messages=[{"role": "user", "content": "ping"}])const RETRYABLE = new Set([429, 500, 502, 503]);
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const backoff = (i) => Math.min(30_000, 1000 * 2 ** i) + Math.random() * 1000;
export async function chat(body, attempts = 5) {
for (let i = 0; i < attempts; i++) {
let res;
try {
res = await fetch("https://api.oneaiapikey.com/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.RELAYKIT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
} catch (err) {
if (i === attempts - 1) throw err; // network error
await sleep(backoff(i));
continue;
}
if (res.ok) return res.json();
if (!RETRYABLE.has(res.status) || i === attempts - 1) {
throw new Error(`${res.status} ${res.headers.get("x-request-id")}: ${await res.text()}`);
}
const hint = Number(res.headers.get("retry-after"));
await sleep(hint > 0 ? hint * 1000 : backoff(i));
}
}Getting help#
If an error persists, email support@relaykit.dev with:
- the request id (from the
X-Request-Idheader or the end of the error message); - the time of the request (with timezone), the endpoint and the model id;
- the HTTP status and the full error message;
- the key's name or its last four characters. Never send the full key.
curl -i https://api.oneaiapikey.com/v1/chat/completions \
-H "Authorization: Bearer $RELAYKIT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "claude-haiku-4-5", "messages": [{"role": "user", "content": "ping"}]}'