Errors
How Tase API errors are shaped, what every HTTP status code means, and how to retry safely with exponential backoff.
Every failed request returns a machine-readable error body alongside its HTTP status code. Branch on the status code for control flow, use the code field for precise handling, and show the message to humans — never parse the message string in code.
Error body
All errors share one shape: a top-level error object with three string fields.
{
"error": {
"message": "Invalid API key provided.",
"type": "authentication_error",
"code": "invalid_api_key"
}
}- **message** (string) — Human-readable description of what went wrong. Safe to log and surface to developers; not stable enough to match against.
- **type** (string) — The error category, such as authentication_error or rate_limit_error. One type per HTTP status.
- **code** (string) — The specific machine-readable reason, such as invalid_api_key or rate_limit_exceeded. Branch on this for fine-grained handling.
HTTP status codes
- **400** invalid_request_error — The request body is malformed or a parameter is invalid: bad JSON, a missing required field, or an out-of-range value. Fix the request; retrying unchanged will fail again.
- **401** authentication_error — The API key is missing (missing_api_key) or wrong or revoked (invalid_api_key). Check the Authorization header and the key value; do not retry until fixed.
- **404** model_not_found — The requested model ID does not exist or is not available to your key. List valid IDs with GET /v1/models.
- **429** rate_limit_error — Too many requests (rate_limit_exceeded). The response includes a Retry-After header; wait at least that long, then retry with backoff.
- **500** api_error — Something failed on our side. Safe to retry with exponential backoff.
- **503** service_unavailable — The service is temporarily overloaded or under maintenance. Retry with exponential backoff.
Example errors
A request with no Authorization header returns 401:
{
"error": {
"message": "No API key provided. Pass your key in the Authorization header.",
"type": "authentication_error",
"code": "missing_api_key"
}
}A request with a missing required field returns 400:
{
"error": {
"message": "Missing required parameter: 'messages'.",
"type": "invalid_request_error",
"code": "invalid_request_error"
}
}A request past your rate limit returns 429:
{
"error": {
"message": "Rate limit exceeded. Retry after the interval in the Retry-After header.",
"type": "rate_limit_error",
"code": "rate_limit_exceeded"
}
}Which errors to retry
- **Retry** — 429, 500, and 503. These are transient. Honor Retry-After on 429, and use exponential backoff for all three.
- **Do not retry** — 400, 401, and 404. These are deterministic: the same request will fail the same way until you fix the key, the body, or the model ID.
Retry with exponential backoff
Double the wait between attempts, add random jitter so parallel workers do not retry in lockstep, and respect Retry-After when the server sends one:
async function completeWithRetry(payload, maxAttempts = 5) {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const res = await fetch('https://api.tase.app/v1/chat/completions', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.TASE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});
if (res.ok) return res.json();
// Deterministic failures: surface immediately, never retry.
if (![429, 500, 503].includes(res.status)) {
const { error } = await res.json();
throw new Error(`${res.status} ${error.type}: ${error.message}`);
}
// Transient failure: honor Retry-After, else exponential backoff + jitter.
const retryAfter = Number(res.headers.get('Retry-After'));
const backoff = 1000 * 2 ** attempt + Math.random() * 250;
const waitMs = retryAfter > 0 ? retryAfter * 1000 : backoff;
await new Promise((resolve) => setTimeout(resolve, waitMs));
}
throw new Error('Request failed after maximum retry attempts.');
}Cap your retries
Persistent 429s