Skip to content
All pages
  1. Overview
  2. Quick start
  3. Authentication
  4. API reference
    1. GET /media
    2. POST /media
    3. GET /media/{id}/preview
    4. GET /integrations/google/connect
    5. GET /integrations/google
    6. DELETE /integrations/google
    7. GET /integrations/google/folders
    8. POST /media/import
    9. GET /media/imports/{id}
    10. POST /renders
    11. GET /renders/{id}
    12. GET /renders/{id}/download
    13. GET /usage
  5. Code examples
  6. Error handling
  7. Rate limits & quotas
  8. Troubleshooting
  9. Changelog

Reference

Error handling

Every failure has the same body, whatever went wrong. The codes are grouped below by what you should actually do about them.

HTTP/1.1 403 Forbidden
X-Request-Id: 0f1c8f6a-…

{
  "error": {
    "code": "insufficient_scope",
    "message": "This API key is not permitted to perform this action."
  },
  "request_id": "0f1c8f6a-…"
}
Match on the code, never on the message. Codes are a frozen, closed set — what is below is all of them, and one is never added without a changelog entry. Messages are wording and may be reworded at any time.

Fix the request

12 codes

Something about what you sent is wrong. Retrying it unchanged will fail identically.

  • missing_api_key401

    No Authorization header was sent.

  • invalid_request400

    The body could not be parsed.

  • validation_failed422

    A field is invalid — including any field that is not in the accepted shape.

  • unsupported_render_type422

    Only aesthetic_quote is available via the API.

  • unsupported_media_type415

    The upload is not a JPEG, PNG, WebP or GIF image (checked from the file bytes, not the header).

  • payload_too_large413

    The uploaded image is empty or over the 15 MB limit.

  • unknown_media404

    That media_id is not in your library, or is archived. Re-list GET /api/v1/media.

  • idempotency_conflict409

    That Idempotency-Key was already used with a different body.

  • integration_not_connected409

    No Google Drive connection for this account. Start GET /api/v1/integrations/google/connect first.

  • render_not_found404

    No render with that id for this account.

  • unknown_folder404

    The Drive folder was not found, or your connection cannot access it.

  • import_not_found404

    No import with that id for this account.

Fix the key

4 codes

The request is fine; the credential is not. Mint a new key, or re-mint it with the scope it needs.

  • invalid_api_key401

    The key is malformed or not recognised. Deliberately the same code for an unknown key and a wrong secret.

  • api_key_revoked401

    The key was revoked. Mint a new one.

  • api_key_expired401

    The key passed its expiry.

  • insufficient_scope403

    The key lacks the scope for this call. Re-mint it with the scope.

Wait — you are at a ceiling

2 codes

Nothing is broken. You have hit a limit and it will clear on its own. Back off and retry rather than hammering.

  • rate_limit_exceeded429

    Too many requests per minute for this key.

  • spend_cap_reached402

    This key's daily spend ceiling is used up. It rolls off per render over 24h.

Safe to retry

5 codes

A transient fault on our side. Nothing was charged. Retry with backoff.

  • caption_prep_failed502

    The caption image could not be composited, so nothing was started and nothing was charged.

  • idempotency_in_progress409

    A previous request with that Idempotency-Key is still being resolved. Retry with the SAME key — a new key would create a second render. Carries Retry-After.

  • asset_unavailable404

    The finished file is not available yet, or not available at all.

  • internal_error500

    A fault on our side.

  • service_unavailable503

    Temporarily unavailable, or a dependency could not be reached.

An account setting — talk to us

4 codes

Nothing you can change from the client. Retrying will not help; the answer is a conversation.

  • tenant_inactive403

    The account is not active.

  • tier_not_eligible403

    The plan cannot hold API keys. Any paid plan can; Free cannot.

  • api_disabled403

    API access is switched off for this account.

  • quota_exceeded402

    The account's render allowance for the cycle is used up — or the plan does not include this render type at all. On Starter it is the latter, always.

Reserved — v1 never returns these

4 codes

In the frozen taxonomy, emitted by no shipped code path. Listed so a client switching on `code` knows the whole set exists, and marked so nobody builds handling for a case that cannot happen.

  • forbidden403

    Reserved. v1 answers an object that is not yours with 404, never 403.

  • render_conflict409

    Reserved for render mutation. v1 exposes no endpoint that mutates a render.

  • method_not_allowed405

    Wrong HTTP method. Reserved — an unmatched method is handled by the router, not this code.

  • concurrency_limit_reached429

    Reserved for a per-key in-flight ceiling. v1 does not return it.


Failures after a render is accepted

A render that was accepted and then failed does not report it as an HTTP status — the call that created it already returned 202. It arrives in render.error when you poll:

  • render_failedin body

    Generation ran and did not produce a usable video.

  • render_timeoutin body

    Generation exceeded its time budget.

  • render_rejectedin body

    The request was refused before generation began.

  • render_canceledin body

    The render was canceled.

Retrying a create safely

A create you are unsure about is the one case to be careful with. Retry it with the same Idempotency-Key and you get the original render back instead of a second one you also pay for, with an Idempotency-Replayed: true header telling you that is what happened. The same key with a different body is 409 idempotency_conflict — that is the guard working, not a bug.

A key remembers the outcome, not just the successes. If the create failed, the same key replays that same error rather than starting a second attempt — so send a new Idempotency-Key when you actually want to try again. Keys expire after 24 hours; after that the same value is free to reuse.