Guides
Error Handling
Best practices for handling API errors in your application.
Error shape
Errors are always JSON with an error string; validation failures also include a details array with one message per invalid field:
// 400 — validation
{ "error": "Validation failed", "details": ["email is required", "password must be at least 8 characters"] }
// 401 — auth
{ "error": "Invalid token" }
// 429 — rate limit
{ "error": "API key rate limit exceeded" }// 400 — validation
{ "error": "Validation failed", "details": ["email is required", "password must be at least 8 characters"] }
// 401 — auth
{ "error": "Invalid token" }
// 429 — rate limit
{ "error": "API key rate limit exceeded" }See Error Explorer for the complete list of status codes and error messages by endpoint.
What to retry, and how
Retry 429 and 5xx — these are usually transient. Don't retry 400/401/403/404 without changing the request first — the same request will fail the same way every time.
async function requestWithRetry(url, options, maxAttempts = 3) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
const res = await fetch(url, options);
if (res.ok) return res.json();
const retryable = res.status === 429 || res.status >= 500;
if (!retryable || attempt === maxAttempts) {
const body = await res.json().catch(() => ({}));
throw new Error(body.error || `Request failed: ${res.status}`);
}
// Exponential backoff with jitter: 1s, 2s, 4s...
const delay = 2 ** (attempt - 1) * 1000 + Math.random() * 250;
await new Promise((resolve) => setTimeout(resolve, delay));
}
}async function requestWithRetry(url, options, maxAttempts = 3) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
const res = await fetch(url, options);
if (res.ok) return res.json();
const retryable = res.status === 429 || res.status >= 500;
if (!retryable || attempt === maxAttempts) {
const body = await res.json().catch(() => ({}));
throw new Error(body.error || `Request failed: ${res.status}`);
}
// Exponential backoff with jitter: 1s, 2s, 4s...
const delay = 2 ** (attempt - 1) * 1000 + Math.random() * 250;
await new Promise((resolve) => setTimeout(resolve, delay));
}
}Handling 401 specifically
A 401 from an end-user Bearer token usually means the token expired, was revoked (password change, logout), or is otherwise invalid — refresh it once using the user's refreshToken and retry the original request before falling back to signing the user out. See User Authentication.
A 401 from an API key almost always means the key is wrong, deleted, or expired — there's no refresh flow for keys; generate a new one from the Console.
details array on a 400 usually tells you exactly which field is wrong, without needing to guess from the status code alone.