Error response shape
{
"success": false,
"error": {
"code": "INPUT_INVALID_FORMAT",
"message": "Field 'platforms' must be a non-empty array of strings.",
"details": {
"field": "platforms",
"received": null,
"expected": "string[]"
}
},
"meta": {
"request_id": "req_01HKZ2Q9YX..."
}
}- code — Stable identifier. Branch your error handling on this, not on
messageor status code. - message — Human-readable. Safe to surface to logged-in developers; do not show to end users without translation.
- details — Code-specific structured info. Empty object if the code has no extra context.
- meta.request_id — Always include this when contacting support — we can trace any request from this ID.
Error code reference
| Code | Status | Meaning | Client action |
|---|---|---|---|
AUTH_INVALID_KEY | 401 | API key missing, malformed, expired, or revoked. | Fix request, then retry |
AUTH_FORBIDDEN | 403 | Key is valid but lacks permission for the requested resource (admin-only endpoint, plan gating, etc.). | Fix request, then retry |
AUTH_RATE_LIMITED | 429 | Per-minute request ceiling exceeded. Respect Retry-After. | Retry with backoff |
INPUT_INVALID_FORMAT | 400 | Request body failed schema validation. The details field lists which fields are wrong. | Fix request, then retry |
INPUT_UNSUPPORTED_PARAM | 400 | Recognized field but value is outside the allowed enum / range. | Fix request, then retry |
CREDIT_INSUFFICIENT | 402 | Account has fewer credits than this operation costs. Top up or upgrade. | Fix request, then retry |
RESOURCE_NOT_FOUND | 404 | Job ID, video ID, or other resource doesn't exist or belongs to a different account. | Don't retry |
RESOURCE_ALREADY_EXISTS | 409 | Reused an Idempotency-Key with a different request body. Generate a fresh UUID. | Fix request, then retry |
RESOURCE_CONFLICT | 409 | State machine conflict (e.g., cancelling a job that already completed). | Don't retry |
PROCESS_EXTERNAL_API_ERROR | 502 | An upstream provider (OpenAI, ElevenLabs, VideoGenAPI, etc.) returned an error. Credits are refunded for permanent failures. | Retry with backoff |
PROCESS_TIMEOUT | 504 | Upstream provider exceeded our timeout. Credits are refunded. | Retry with backoff |
SERVER_INTERNAL_ERROR | 500 | Unexpected server-side error. Sentry-logged. Safe to retry with the same Idempotency-Key. | Retry with backoff |
SERVER_UNAVAILABLE | 503 | Maintenance or capacity issue. Retry with exponential backoff. | Retry with backoff |
Retry decision tree
- Status
2xx→ success.success: truein body. - Status
4xx, code starts withAUTH_,INPUT_,CREDIT_, orRESOURCE_NOT_FOUND→ fix the request, then retry. Do not retry blindly. - Status
409 RESOURCE_ALREADY_EXISTS→ idempotency key collision. Generate a fresh UUID, retry. - Status
429→ respectRetry-After. See rate limits. - Status
5xx→ retry with exponential backoff (recommended: 1s, 2s, 4s, 8s, 16s, cap at 60s). Use the same Idempotency-Key across retries so credits aren't double-charged.
Handler pattern (TypeScript)
type ApiError = {
success: false;
error: { code: string; message: string; details?: Record<string, unknown> };
meta: { request_id: string };
};
async function callApi<T>(url: string, body: unknown, idempotencyKey: string): Promise<T> {
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.REELSBUILDER_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify(body),
});
const json = await res.json();
if (!res.ok) {
const err = json as ApiError;
switch (err.error.code) {
case "CREDIT_INSUFFICIENT":
throw new Error("Out of credits — prompt user to upgrade");
case "AUTH_INVALID_KEY":
case "AUTH_FORBIDDEN":
throw new Error("Auth misconfigured");
case "AUTH_RATE_LIMITED":
// caller should respect Retry-After
throw new Error("Rate limited");
case "RESOURCE_ALREADY_EXISTS":
throw new Error("Idempotency collision — regenerate key");
default:
throw new Error(`${err.error.code}: ${err.error.message} (req ${err.meta.request_id})`);
}
}
return json as T;
}Versioning
New error codes can be added in any minor release. Existing codes are never renamed or repurposed. If a code's meaning must change, a new code is added and the old one is deprecated (returned for at least 12 months alongside the replacement).
Build your error handling with a default branch that logs unfamiliar codes rather than crashing.
Reporting an issue
Email admin@reelsbuilder.ai with:
- The
meta.request_idfrom the failing response - The
error.codeand approximate timestamp (UTC) - What you were trying to accomplish