Start here
Errors & limits
Every error is JSON with "error", a machine-readable "error_code" and a "message", and an honest status code. Nothing is charged on a request that fails validation, auth, or the balance check.
Error shape
{
"error": "string",
"error_code": "UPPER_SNAKE", // the code to branch on
"message": "human-readable", // always present
"field": "resolution", // when one field is at fault
...details // e.g. credits_required, max_active_jobs
}- Since 1.1.1 every failure on every endpoint has
error,error_code(UPPER_SNAKE) andmessage. Branch onerror_codeand the HTTP status. erroris kept for older integrations: failures that existed before 1.1.0 keep their olderrorstring (for exampleUnauthorizedwitherror_codeUNAUTHORIZED, orNot foundwithNOT_FOUND) and HTTP status; newer failures use the code aserrortoo. Some bodies also carry a lowercasecode(for examplequote_mismatch,no_speech); don't branch on it.
{ "error": "resolution must be \"480p\" or \"720p\"", "error_code": "INVALID_RESOLUTION",
"message": "…", "field": "resolution" }
{ "error": "INSUFFICIENT_CREDITS", "error_code": "INSUFFICIENT_CREDITS",
"credits_required": 125, "credits_balance": 40, "message": "…" }
{ "error": "QUOTE_MISMATCH", "error_code": "QUOTE_MISMATCH", "code": "quote_mismatch",
"credits_quoted": 125, "message": "…" }
{ "error": "Unauthorized", "error_code": "UNAUTHORIZED", "message": "The API key is not valid" }Status codes
| Code | Meaning | What to do |
|---|---|---|
| 400 | Validation failed — a field is missing, malformed, or over a cap. | Fix the request; error_code and field name the problem. |
| 401 | UNAUTHORIZED — missing or invalid API key. | Check the header format in Authentication. |
| 402 | INSUFFICIENT_CREDITS — balance below the quote. | Top up, or shorten the script / lower the resolution. |
| 404 | NOT_FOUND — resource not found, or not yours. | Verify the id came from your own account. |
| 409 | Conflict: quote mismatch, idempotency key reused, voice cap, voice unavailable, free clone attempts used up, edit rules. | See the code tables below. |
| 422 | An input URL could not be read (audio probe, selfies, samples without speech). | Make sure the URLs are public https and serve real media. |
| 429 | TOO_MANY_ACTIVE_JOBS — concurrent job cap hit. | Wait for a running job to finish, then re-queue. |
| 502 | CLONE_UNAVAILABLE — voice cloning is temporarily unavailable. | Retry later; the charge is refunded. |
| 503 | TRY_AGAIN — briefly unavailable. | Nothing was charged. Retry with the same idempotency key. |
| 5xx | Something failed on our side. | Safe to retry reads; retry POSTs with an idempotency key. |
Asking for another client's character, voice, asset, or job returns the same 404 as an id that was never created.
Codes on /videos and /videos/estimate
| Status | error (kept from before 1.1.0) | error_code |
|---|---|---|
| 400 | Invalid JSON body | INVALID_BODY |
| 400 | mode must be "talking_head" or "scenes" | INVALID_BODY |
| 400 | character_id is required | INVALID_BODY |
| 400 | resolution must be "480p" or "720p" | INVALID_RESOLUTION |
| 400 | Provide exactly one audio source: { voice_id, script } or { audio_url } | INVALID_BODY |
| 400 | video_idea is required unless first_frame_url is provided | INVALID_BODY |
| 400 | script too long | AUDIO_TOO_LONG |
| 422 | Could not probe audio_url duration: … | INVALID_URL |
| 404 | Not found | NOT_FOUND |
| 402 | INSUFFICIENT_CREDITS | with credits_required, credits_balance |
| 429 | TOO_MANY_ACTIVE_JOBS | with max_active_jobs |
| 500 | Failed to start workflow | START_FAILED (refunded) |
| 500 | Failed to queue job | INTERNAL |
New in 1.1.0 (here error = error_code):
| error_code | When |
|---|---|
INVALID_VIDEO_MODE | video_mode is not reference | text. |
INVALID_VOICE_MODE | voice_mode is not voice | none. |
INVALID_DURATION | target_seconds missing or outside 5–60. |
INVALID_SCRIPT | Script empty or over 4,000 characters. |
INVALID_SCENE_LINES | scene_lines not 1–15 strings of ≤600. |
INVALID_SCENE_PHOTOS | scene_photos malformed or over 12 items. |
INVALID_VOICE_SETTINGS | voice_settings malformed. |
AUDIO_TOO_SHORT | Audio under 1 s. |
INVALID_URL | A private, link-local or otherwise non-public URL. |
FIRST_FRAME_KIND_MISMATCH | first_frame_asset_id was made for the other video type. |
QUOTE_MISMATCH | 409 — quote above expected_credits; body has credits_quoted. |
TRY_AGAIN | 503 — retry; nothing charged. |
POST /images/first-frame keeps 500 First-frame generation failed with error_code FIRST_FRAME_NOT_VERTICAL, CHARACTER_IMAGE_UNAVAILABLE, PROVIDER_* or INTERNAL; its 400s keep their old sentence in error (error_code INVALID_BODY or INVALID_SCENE_PHOTOS), its 404 is NOT_FOUND and its 402 INSUFFICIENT_CREDITS. Edit errors are listed in Video edits; failed-job codes in Jobs & polling.
Warnings
A 200 can carry warnings: [{ field, code, message }] — the request worked, but something was adjusted:
| code | Meaning |
|---|---|
RESOLUTION_MAPPED | 720p was served as 768P. |
FIELD_IGNORED | A legacy field, a field with no effect (topic, audience), a field that doesn't apply (e.g. voice_id on a silent video), a non-https first_frame_url / audio_url, or a selfie that failed the URL check. |
SCENE_PHOTOS_TRUNCATED | More than 3 scene photos; the first 3 were kept. |
LOW_SAMPLE_AUDIO | Voice clone made from under 25 s of usable speech. |
SCENE_PHOTO_DROPPED | Job-time, scene videos: a scene photo couldn't be fetched safely, so it wasn't used or charged. |
Warnings found while a job runs (like SCENE_PHOTO_DROPPED) are listed on that job's row in GET /usage/jobs as metadata.warnings, with the same { field, code, message } shape.
Idempotency
Send Idempotency-Key (1–255 visible ASCII characters) or client_request_id (uuid). Optional on /videos; required on fix-scene, edit-script and fix-sound unless dry_run.
- Same key, same body →
200with{ workflow_id, status, duplicate: true, credits_reserved }; nothing is charged twice. - One key covers all
/videosjob types: a talking-head body and then a scenes body under the same key →409 IDEMPOTENCY_KEY_REUSED(one key, one job across both modes). - On
/videosthe body is validated and priced again before the duplicate lookup, so a retry can get409 QUOTE_MISMATCH(re-confirm and resend with the newexpected_creditsunder the same key) or a404/422if something it referenced is gone. Edits answer the duplicate first. - Keys are scoped per client and per endpoint family — a
/videoskey never collides with a fix-scene key.
| Status | error | When |
|---|---|---|
| 400 | IDEMPOTENCY_KEY_MISMATCH | Header and client_request_id are both sent and differ. |
| 400 | IDEMPOTENCY_KEY_INVALID | The key or client_request_id is malformed (key: 1–255 visible ASCII; client_request_id: a uuid). |
| 400 | IDEMPOTENCY_KEY_REQUIRED | An edit without a key (and not a dry run). |
| 409 | IDEMPOTENCY_KEY_REUSED | Same key, different body. |
Account limits
| Limit | Default | Where it bites |
|---|---|---|
| Active video jobs at once | 5 | 429 from POST /videos and edits — queued + running jobs count. |
| Cloned voices | 3 | 409 VOICE_LIMIT_REACHED — delete one to free a slot. |
| Library voices | 10 | Separate cap, same 409 when full. |
| Voiced video audio | 1–59.5 s | AUDIO_TOO_SHORT / AUDIO_TOO_LONG at queue time. |
| Silent scene video | 5–60 s | target_seconds; else INVALID_DURATION. |
| Video script | 4,000 characters | INVALID_SCRIPT |
| TTS text | 5,000 characters | 400 from POST /tts. |
| Open edits per video | 1 (an edit idle for 30 min no longer blocks) | 409 EDIT_IN_PROGRESS |
| Quiet job | 75 minutes | Failed with STALE_TIMEOUT and refunded. |
| List page size | 100 rows max | limit values above 100 are capped at 100 on assets / usage lists. |
Retry guidance
- GETs are always safe to retry.
- Send an idempotency key on every POST /videos. Then a timeout or a
503 TRY_AGAINis safe to retry with the same key and body — you get the original job back, not a second one. - A failed job is refunded automatically. Check its
error_code(list) and re-queue with a new key.