REST API
The same operations the MCP server exposes are available as plain HTTP for scripts and back-ends.
Base URL: https://www.filmera.ai/api/v1 · OpenAPI: /api/v1/openapi.json
Authentication
Authorization: Bearer fk_xxxxxxxx_…
Create keys under Settings → API keys (REST API keys). A key is shown once; store it like a password. A key spends your Filmera credits; you can set a spending cap on it and revoke it at any time.
Supabase OAuth access tokens (from the MCP sign-in flow) are accepted too.
API keys are for the REST API only — scripts, CI and your own back-end. The
MCP server accepts OAuth sign-in only and refuses API keys with
401 api_key_not_supported.
Endpoints
| Method & path | What it does |
| --- | --- |
| GET /me | Who you are, plan, credit balance, this key's spending cap (null = no cap), and approval_required |
| GET /projects · POST /projects | List / create projects |
| GET /projects/{id} | Project context: canvas nodes, connections, scenes, timelines, best-take URLs |
| GET /projects/{id}/media | Media pool with takes (?kind=gen|ref|audio) |
| GET /models | Image, video and audio models |
| GET /jobs/{id} | Job status; done includes result URLs |
| GET /jobs?ids=a,b&wait=50 | Long-poll several jobs (max 50 s). Stop when all_done is true; otherwise wait next_poll_after_s before the next call |
| POST /tools/{name} | Run any tool from the MCP catalog with a JSON body |
POST /tools/{name} covers everything else — creating nodes, importing media
from a URL (import_media_from_url), generating, assembling the timeline,
exporting, reading your own in-app chat with the Filmera agent
(get_chat_history, newest first — pass next_before as before for older
messages), reading and copying public templates (list_templates,
get_template, duplicate_template). The body is the tool's input (see the
OpenAPI document for schemas). Responses:
200 { "result": … }— JSON for data tools, a text summary for editing tools422 { "error", "code": "tool_failed" }— the tool refused (bad input, node already generated, empty timeline, …). Fix the request and retry.
Errors
Every error is { "error": "<message>", "code": "<code>" }.
| Status | Code | Meaning |
| --- | --- | --- |
| 401 | unauthenticated · invalid_token | Missing or invalid Bearer |
| 401 | key_reauth_required | The key needs reconnecting — click Reconnect next to it under Settings → API keys. The key itself stays the same |
| 401 | key_revoked | The key was revoked |
| 401 | api_key_not_supported | An API key was sent to the MCP endpoint (/api/mcp). Remove the header and connect with OAuth — keys work only here, on /api/v1 |
| 402 | budget_exceeded · insufficient_credits | This key or agent reached the spending cap you set (see Reserved credits below), or the balance is short |
| 403 | forbidden · grant_revoked | Scope missing or access disconnected |
| 409 | idempotency_in_progress · idempotency_unknown · earlier_run_failed | An earlier call with the same Idempotency-Key or quote_id is still running (retry after Retry-After), stopped without reporting back, or produced nothing — see Retries below |
| 422 | idempotency_key_reused | That Idempotency-Key was already used for a different request — use a new key |
| 429 | rate_limited | Slow down; see Retry-After |
| 503 | retry | Token refresh in progress — retry after a moment |
Spending needs the user's go-ahead. With the check on (the default), POST /tools/generate_*
returns 200 with { "result": { "status": "confirmation_required", "quote_id", "credits", "items", … } }
and runs nothing. Ask the user, then call again with the same body plus quote_id and
user_confirmation (their reply, word for word).
Retries
A generate call that times out may still have started. Send it again unchanged — with the
same quote_id, or with the same Idempotency-Key header (any unique string up to 255
visible characters, e.g. a UUID; keep it the same on the quote call and the confirming call).
Within 24 hours a repeated request returns the original result with status: "already_started"
(or "already_ran") and the original job_ids, adds the header Idempotent-Replayed: true, and
charges nothing. While the first call is still being processed you get 409
idempotency_in_progress with Retry-After. If every job of the earlier run failed (and was
refunded), the repeat answers 409 earlier_run_failed once and the key is free again. Use a
new key for each new generation. Keys are shared by all of your API keys and agents, so use
random values (a UUID) rather than counters.
Reserved credits
Most generations cost a known number of credits, charged once. A few audio models decide their own output length, so the price cannot be known before the job runs. If you set a spending cap, it reserves the model's maximum when the job is queued and returns the unused part as soon as the real length is known — so a short result counts against the cap only for what it actually used.
This matters for concurrency, not for totals: several such jobs in flight at
once can hit budget_exceeded even though each one settles far below its
reservation. Queue them in sequence, or raise the cap. Keys without a cap are not
affected.
Example
BASE=https://www.filmera.ai/api/v1
KEY=fk_…
# 1. a project
PROJECT=$(curl -s $BASE/projects -H "Authorization: Bearer $KEY" | jq -r '.projects[0].id')
# 2. an image node with your own prompt
NODE=$(curl -s $BASE/tools/create_image_node -H "Authorization: Bearer $KEY" \
-H 'content-type: application/json' \
-d "{\"project_id\":\"$PROJECT\",\"title\":\"Hero\",\"prompt\":\"portrait of a tired astronaut, 35mm\",\"asset_kind\":\"character\"}" \
| jq -r '.result.node_id')
# 3. generate it (charged to your credits)
curl -s $BASE/tools/generate_ref_nodes -H "Authorization: Bearer $KEY" \
-H 'content-type: application/json' \
-d "{\"project_id\":\"$PROJECT\",\"node_ids\":[\"$NODE\"]}"
# 4. see the result on the node
curl -s $BASE/projects/$PROJECT -H "Authorization: Bearer $KEY" | jq '.nodes[] | select(.id=="'$NODE'")'
Generation is asynchronous — wait on GET /jobs?ids=…&wait=50 until all_done
is true (done, failed and not_found are all final; unknown ids are listed in
missing). A failed generate call answers with job_ids: [], all_done: true,
error and retryable — there is nothing to poll, fix the call or ask the user.
A finished generation job also carries credits (charged, refunded, net,
read from the credit ledger), and a failed one a retry_hint sentence — failed
generations are refunded automatically, and retrying is a new charge.
Last updated 2026-10-25. Prices, model specs and credit costs on this page are read from the live catalogue at build time.
