Errors & error codes
The unified error JSON shape, the complete error code list, and retry strategy.
Errors & error codes
Every error returns a non-2xx HTTP status with this body shape:
{
"error": {
"code": "insufficient_credit",
"message": "Your credit balance is insufficient for this request."
}
}
code is a stable, machine-readable string — branch on it. message is human-readable and its wording may change at any time, so never match on it.
Error code reference
| HTTP | code | Meaning | What to do |
|---|---|---|---|
| 400 | invalid_request | Malformed request: missing required fields, unparseable JSON, wrong parameter type or range. | Fix the request. Retrying will not help. |
| 401 | unauthorized | No Authorization header, or not in Bearer <key> form. | Send the correct header. |
| 401 | invalid_api_key | The key does not exist, was deleted, or is malformed. | Check the key is complete and free of stray whitespace, or create a new one in the Dashboard. |
| 403 | api_key_disabled | The key has been disabled. | Re-enable it in the Dashboard, or use a different key. |
| 403 | model_not_allowed | The model exists but is not on this key's allowlist. | Adjust the key's model allowlist, or use a permitted model. |
| 404 | model_not_found | No such model ID on the platform. | Confirm the ID with GET /v1/models. Remember the provider/model prefix. |
| 402 | insufficient_credit | The account balance cannot cover this request. | Top up in the Dashboard. |
| 402 | spend_limit_reached | The key's monthly spend limit has been reached. | Raise the limit, or wait for the automatic monthly reset. |
| 429 | rate_limited | Request rate exceeded. | Back off per Retry-After and retry. See Rate limits. |
| 502 | upstream_error | The upstream provider returned an error, timed out, or is temporarily unavailable. | Safe to retry with exponential backoff, or fail over to another model. |
Examples
HTTP/1.1 402 Payment Required
Content-Type: application/json
{
"error": {
"code": "spend_limit_reached",
"message": "This API key has reached its monthly spend limit of $50.00."
}
}
HTTP/1.1 404 Not Found
Content-Type: application/json
{
"error": {
"code": "model_not_found",
"message": "Model 'gpt-4o' does not exist. Did you mean 'openai/gpt-4o'?"
}
}
Should you retry?
| Category | Codes | Retry? |
|---|---|---|
| Client errors | invalid_request, model_not_found | No. Fix the code first. |
| Auth problems | unauthorized, invalid_api_key, api_key_disabled, model_not_allowed | No. Needs human action. |
| Billing problems | insufficient_credit, spend_limit_reached | No. Needs a top-up or a limit change. |
| Transient problems | rate_limited, upstream_error | Yes. Exponential backoff with jitter. |
Handling errors (Python)
The openai SDK raises on non-2xx responses; the error body is available via err.response.json().
import os
import time
import httpx
from openai import OpenAI, APIStatusError
client = OpenAI(
base_url="https://api.alphacurve.io/v1",
api_key=os.environ["INFERENCE_API_KEY"],
timeout=httpx.Timeout(60.0),
)
RETRYABLE = {"rate_limited", "upstream_error"}
def complete(**kwargs):
for attempt in range(5):
try:
return client.chat.completions.create(**kwargs)
except APIStatusError as err:
try:
code = err.response.json()["error"]["code"]
except Exception:
code = "unknown"
if code not in RETRYABLE:
raise
retry_after = err.response.headers.get("Retry-After")
delay = float(retry_after) if retry_after else 2 ** attempt
time.sleep(delay)
raise RuntimeError("retries exhausted")
Handling errors (Node / TypeScript)
import OpenAI, { APIError } from "openai";
const client = new OpenAI({
baseURL: "https://api.alphacurve.io/v1",
apiKey: process.env.INFERENCE_API_KEY!,
});
const RETRYABLE = new Set(["rate_limited", "upstream_error"]);
async function complete(
params: OpenAI.Chat.ChatCompletionCreateParamsNonStreaming,
) {
for (let attempt = 0; attempt < 5; attempt++) {
try {
return await client.chat.completions.create(params);
} catch (err) {
if (!(err instanceof APIError)) throw err;
const code = (err.error as { code?: string } | undefined)?.code ?? "unknown";
if (!RETRYABLE.has(code)) throw err;
const wait = 2 ** attempt * 1000 + Math.random() * 250;
await new Promise((r) => setTimeout(r, wait));
}
}
throw new Error("retries exhausted");
}
Debugging with curl
-i prints the status line, headers and body together:
curl -i https://api.alphacurve.io/v1/chat/completions \
-H "Authorization: Bearer $INFERENCE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "model": "does-not-exist", "messages": [] }'
When reporting a problem, include the response id (if present), the code, the timestamp and the model — that is enough for us to locate the request.
Errors during streaming
Once a stream starts the HTTP status is already 200. A later failure is delivered as an SSE event:
data: {"error":{"code":"upstream_error","message":"Upstream provider timed out."}}
data: [DONE]
So your stream parser must check each chunk for an error field in addition to choices.