Skip to main content
Errors come back in one of two shapes:

HTTP-level errors (request failed)

Returned by the API router itself. Always a structured JSON body:

Run-level errors (run failed)

A run that got past validation but failed mid-flight has status: "failed" with machine-readable error_code + human error_message. Your polling code should branch on status first, then inspect these.

Full error code reference

No Authorization: Bearer ... header on the request.Fix: attach the header. See authentication.
The prefix couldn’t be found or the bcrypt secret didn’t match.Fix: double-check you copied the key without truncation. Keys are hck_live_<12>.<32> with a single dot. If the secret itself looks wrong, mint a new key in Settings → API Keys.
The key was revoked in the UI.Fix: create a new key. Revocation is permanent — the secret is destroyed on revoke.
The key authenticates but doesn’t carry the scope required by the endpoint.Fix: mint a new key with the right scopes. The default scopes (runs:write, runs:read) cover almost everything. You only need flows:read explicitly if you’re listing or fetching saved flows.
The API key has hit its configured monthly credit cap. Resets on the first UTC day of next month.Fix: raise the cap in Settings → API Keys, or wait for the reset. To prevent this in the future, combine a cap with autorefill — the cap protects against runaway loops; autorefill keeps legitimate usage from stalling.
Your account balance dropped below the cost of the next reservation mid-run.Fix: enable autorefill, or top up manually and re-trigger the run with a new Idempotency-Key. Existing reservations on the failed run are released automatically.
The flow_id you passed doesn’t exist or isn’t accessible to the key’s owner.Fix: verify with GET /flows. Flows in another user’s account (unless public) aren’t visible.
No run with that ID for the authenticated user.Fix: check you’re polling the same key that created the run. Runs are scoped per-user.
You tried to cancel a run that’s already completed, failed, or cancelled.Fix: nothing to do — the run is already in a terminal state.
POST /runs body has neither flow_id nor flow_schema.Fix: supply one. See runs.
You supplied both flow_id and flow_schema. They’re mutually exclusive.Fix: pick one.
The inline flow_schema failed structural validation — missing nodes, bad node types, malformed edges, cycles, etc.Fix: the response body includes Zod error details. The same validator runs in the visual editor, so any flow that loads in the UI will accept here.
inputs.prompts[N] or inputs.media[N] references a step index that doesn’t exist in the flow (or isn’t a non-negative integer).Fix: call GET /flows/:id and use the required_inputs[].step_order values it returns.
You sent a prompt to a non-text step, or media to a non-media_input step.Fix: send prompts only to text_input / text_editor steps and media only to media_input steps. GET /flows/:id returns the correct mapping in required_inputs.
A media entry is malformed — missing url, or type isn’t one of image / video / audio.Fix: each media entry must be { "url": "https://…", "type": "image" | "video" | "audio" }.
inputs.media[N].type doesn’t match what the step accepts. For example, sending "image" to a step toggled to accept only video.Fix: read required_inputs[].accepted_media_types from GET /flows/:id and send a matching type.
A node in the flow has a type the worker doesn’t recognize.Fix: this can only happen with a hand-written inline flow_schema. Check the node support matrix — every type the visual editor produces is supported.
Uncaught exception during step execution. Most commonly a transient generation error from one of the AI models (ElevenLabs, an image or video model), a timeout, or a content-policy rejection.Fix: safe to retry with a new Idempotency-Key; credits spent up to the failure are already released.
The final render errored or timed out (10-minute cap).Fix: usually means the composition is too long or the render queue is under heavy load. Retry. If it persists, try shortening the flow (fewer scenes, shorter clip durations) — very long compositions can hit render limits.

Retry decision tree

1

Is it a 4xx that you control?

401, 402, 403, 404, 400 — don’t retry. Fix the request (wrong key, over cap, wrong ID, malformed body) and resend.
2

Is it a 500?

Exponential backoff: 1s, 2s, 4s, 8s. Give up after the 4th attempt. Same Idempotency-Key each time so you don’t duplicate runs.
3

Is the run stuck running for > 10 minutes?

Cancel it with POST /runs/:id/cancel, then retry with a new key.
4

Did the run fail with worker_error or render_failed?

Safe to retry with a fresh Idempotency-Key. The failed run’s reservations are already released.

Catching errors with the SDK

Both SDKs raise typed errors you can branch on by code or status:

Idempotency + retries

Always resend with the same idempotency_key on network errors. The SDKs do not auto-retry today, so wrap your own loop:
The server deduplicates on (user_id, Idempotency-Key), so the second call returns the already-created run instead of making a duplicate.
Don’t reuse idempotency keys across different logical requests. Reusing the key across semantically different bodies means the server ignores your new body and returns the original run’s state.

Contact + escalation

Run into an error that isn’t covered here? Open a support ticket from the app, or email [email protected] with:
  • The run ID
  • The full response body of GET /runs/:id
  • A short description of what you expected