Guides
Errors & retries
Every error code the API returns, what it means, and whether to retry.
Errors use the OpenAI error shape, so the OpenAI SDKs raise them as normal exceptions. Branch on error.code (and error.type), never on the message text.
{
"error": {
"message": "Insufficient balance. Top up to continue.",
"type": "insufficient_quota",
"code": "insufficient_credit",
"param": null
}
}
The message is in English by default. Send Accept-Language: id to receive it in Bahasa Indonesia. code and type are the same in every language.
Error codes
| Status | Code | Type | Meaning | What to do |
|---|---|---|---|---|
| 400 | invalid_request | invalid_request_error | The body is not valid JSON or names no model. | Fix the request. Don't retry. |
| 400 | max_tokens_exceeded | invalid_request_error | max_tokens or max_completion_tokens is above 384,000. | Lower it. Don't retry. |
| 400 | unsupported_content | invalid_request_error | A message contains an image or another non-text part. | Send text only. Don't retry. |
| 401 | invalid_api_key | invalid_request_error | The key is missing, malformed, revoked, or unknown. | Check the header and the key. Don't retry. |
| 402 | insufficient_credit | insufficient_quota | Your available balance is $0.00 or below. | Top up, then retry. |
| 402 | key_budget_exceeded | insufficient_quota | This request would take the key past its monthly budget. | Raise or clear the budget in the console, or wait for the next month (WIB). |
| 403 | insufficient_scope | insufficient_permissions | The key lacks the chat:write scope. | Use a key with the right scope. Don't retry. |
| 404 | model_not_found | invalid_request_error | The model ID is not served. | Use deepseek-flash. Don't retry. |
| 404 | not_found | invalid_request_error | Unknown endpoint. | Check the path; the base URL ends in /v1. |
| 405 | method_not_allowed | invalid_request_error | Wrong HTTP method for the endpoint. | Use POST for chat completions, GET for models. |
| 413 | request_too_large | invalid_request_error | The body is over the request size limit. | Shorten the prompt or conversation history. |
| 429 | rate_limit_exceeded | rate_limit_exceeded | More than 300 requests in a minute on this key. | Wait for Retry-After, then retry. |
| 429 | concurrency_limit_exceeded | rate_limit_exceeded | More than 16 requests in flight on this key. | Wait for Retry-After or for a request to finish. |
| 429 | ip_rate_limited | rate_limit_exceeded | Too many requests from one IP address. | Wait for Retry-After, then retry. |
| 500 | internal_error | api_error | Unexpected server error. | Retry with backoff. |
| 502 | provider_unavailable | api_error | The model provider failed or could not be reached. | Retry with backoff. |
| 503 | billing_unavailable | api_error | Billing is temporarily unavailable; the request was not run. | Retry with backoff. You were not charged. |
| 503 | service_unavailable | api_error | The service is temporarily unavailable. | Retry with backoff. |
Other errors returned by the model provider — for example a 400 for a parameter combination it does not support — are forwarded with the provider's own error body.
When to retry
Retry only 429, 500, 502, and 503. Everything else needs a change to the request, the key, or your balance first.
- 429: wait for the number of seconds in the
Retry-Afterheader before trying again. - 5xx: use exponential backoff with jitter, for example 1 s, 2 s, 4 s, and give up after a few attempts.
- Each retry is a new request. It is held, metered, and billed on its own.
The official OpenAI SDKs already retry 429 and 5xx responses a couple of times with backoff, and honour Retry-After. You can raise the limit:
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.larasrouter.com/v1",
api_key=os.environ["LARAS_API_KEY"],
max_retries=4,
)
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.larasrouter.com/v1",
apiKey: process.env.LARAS_API_KEY,
maxRetries: 4,
});
Handle errors by code
import openai
try:
response = client.chat.completions.create(
model="deepseek-flash",
messages=[{"role": "user", "content": "Hello"}],
)
except openai.APIStatusError as e:
# The SDK exposes error.code from the response body as e.code.
if e.code == "insufficient_credit":
notify_owner("Laras Router balance is empty — top up to resume.")
elif e.code == "key_budget_exceeded":
notify_owner("This key reached its monthly budget.")
else:
raise
Failed requests and charges
- A request rejected before it reaches the model — any
400,401,402,403,404,413,429from Laras Router — is never charged. - A request the model provider rejects with a
4xxis not charged; its hold is released. - A request that fails with a
5xx, times out, or is interrupted keeps its hold while we reconcile it. You may be charged for usage the provider reports for it; otherwise the hold is released without a charge.
Request ID
Responses to requests that reached billing carry an X-Laras-Request-ID header. It identifies the charge and the hold for that request. Include it when you contact support about a charge. Requests rejected before a hold is placed — for example a 401 — may not carry it.