Error shape
{ "error": { "message": "Human-readable explanation.", "type": "insufficient_quota" } }
Status codes
| Status | Type | Meaning | Retry? |
|---|---|---|---|
| 400 | invalid_request_error | Malformed body, missing messages, bad field | No — fix the request |
| 401 | invalid_api_key | Missing, wrong or revoked key | No |
| 402 | insufficient_quota | The key's monthly spend cap is reached | Not until the 1st (UTC), or raise the cap |
| 404 | invalid_request_error | Unknown model id or route | No |
| 503 | service_unavailable | Not live yet, or the router is restarting | Yes, with backoff |
The 400 and 404 type strings follow the OpenAI convention; branch on the HTTP status, which is the stable contract.
Handling errors in Python
import os
import time
import openai
from openai import OpenAI
client = OpenAI(base_url="https://api.failure.fail/v1", api_key=os.environ["FAILURE_API_KEY"], max_retries=0)
def ask(messages, model="failure-tempest-1", attempts=5):
delay = 1.0
for i in range(attempts):
try:
return client.chat.completions.create(model=model, messages=messages)
except openai.APIStatusError as e:
if e.status_code == 402:
# Spend cap reached: stop, tell a human, do not retry.
raise RuntimeError("Monthly spend cap reached for this key.") from e
if e.status_code == 503 and i < attempts - 1:
time.sleep(delay)
delay = min(delay * 2, 30)
continue
raise
Handling errors in TypeScript
import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://api.failure.fail/v1", apiKey: process.env.FAILURE_API_KEY, maxRetries: 0 });
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
export async function ask(messages: OpenAI.ChatCompletionMessageParam[], model = "failure-tempest-1") {
let delay = 1000;
for (let i = 0; i < 5; i++) {
try {
return await client.chat.completions.create({ model, messages });
} catch (e) {
if (e instanceof OpenAI.APIError) {
if (e.status === 402) throw new Error("Monthly spend cap reached for this key.");
if (e.status === 503 && i < 4) {
await sleep(delay);
delay = Math.min(delay * 2, 30000);
continue;
}
}
throw e;
}
}
}
The 503 before launch
Until Failure Tempest 1 is live, chat completions return:
{ "error": { "message": "not live yet", "type": "service_unavailable" } }
Poll GET /v1/status (public, no key) and check live instead of hammering the completions endpoint.
Good habits
- Retry only 503s, with exponential backoff and a ceiling.
- Treat 402 as a budget event: alert someone, do not loop.
- Log
error.typeanderror.message; include them if you write to api@failure.fail.