Error codes

The error envelope, every code the API returns, and when it fires.

Error envelope

Every failure — including schema validation rejections — comes back in this shape, with the HTTP status carrying the same meaning as the code.

json
{
  "error": {
    "code": "CAMPAIGN_NOT_DRAFT",
    "message": "Recipients can only be added to draft campaigns",
    "details": { "...": "present only on some codes" },
    "timestamp": "2026-08-13T12:00:00.000Z",
    "requestId": "req-abc123"
  }
}

Branch on code, never on message — messages are written for humans and are revised freely. requestId is what to quote in a support request.

details is present on a few codes and carries the machine-usable part: retryAfter (seconds) on RATE_LIMITED; shortfallVideos, estimatedShortfallCents and walletVideoRateCents on WALLET_TOPUP_REQUIRED; and a validation array on schema rejections.

Codes

CodeHTTPWhen
UNAUTHORIZED401Missing, malformed or revoked credential.
FORBIDDEN403Authenticated, but not allowed here — e.g. an API key on a dashboard-only endpoint.
VALIDATION_ERROR400Invalid input. Not retryable as sent. Also used at 409 for a few state conflicts (render cancel/retry, campaign unready).
NOT_FOUND404Generic resource miss — renders and library entries.
CAMPAIGN_NOT_FOUND404Unknown campaign id, not yours, or soft-deleted.
RECIPIENT_NOT_FOUND404Unknown recipient, or one under a campaign you do not own.
ASSET_NOT_FOUND404The campaign's base video is missing — usually swept by storage retention. Upload it again.
API_KEY_NOT_FOUND404Unknown or already-revoked API key id.
CAMPAIGN_NOT_DRAFT409A draft-only write (settings, assets, recipients) against a campaign that has left draft.
CAMPAIGN_NOT_READY409A lifecycle action against the wrong status — launching a draft, cancelling a completed campaign, downloading before completion, or a base video still being processed.
RECIPIENT_ALREADY_PROCESSED409Deleting a recipient that is past pending.
CONFLICT409Concurrency and integrity conflicts: deleting a rendering campaign, retrying a processing recipient, base-recording drift on retry, or an idempotent request still in flight (with Retry-After).
IDEMPOTENCY_KEY_REUSED422Same Idempotency-Key, different body. Reuse the original body or mint a new key.
RATE_LIMITED429Over 100 requests/min. Wait for Retry-After and retry.
PLAN_LIMIT_REACHED402 / 403402 when plan render quota is exhausted with no self-serve wallet rate; 403 when the plan does not include the feature at all (API keys, webhooks).
WALLET_TOPUP_REQUIRED402Plan quota and wallet balance together do not cover the request. Add funds or upgrade.
FILE_TOO_LARGE400A video over the 200 MB limit.
INVALID_FILE_TYPE400An upload that is not mp4, webm or quicktime.
INTERNAL_ERROR500Server-side fault. Safe to retry.

What to retry

  • 429 and 500 — retry, honoring Retry-After when present.
  • 409 — retry only after the state actually changes. A CONFLICT carrying Retry-After is the one that is genuinely worth retrying on a timer.
  • 400, 401, 403, 404, 422 — fix the request; retrying it unchanged returns the same error.
  • 402 — terminal until you upgrade or add funds. Send any retry with the same Idempotency-Key: a 402 releases the key, so a corrected retry is allowed rather than replayed.