cinefables Developer API — private beta

Put a film studio inside your product.

One API call turns an idea into a finished film — script, cast, voices, music, subtitles and a final cut in 16:9 and 9:16. Not clips you have to assemble. A film.

Read the quickstart Request access

How it works

Rendering a film takes minutes, not milliseconds. The API is built around that: you submit, we call you back.

1 · Submit

POST an idea or a full script. You get a video id back immediately — the request never blocks on the render.

2 · We render

Story, cast with consistent faces, voices, score, subtitles, and the final cut — assembled end to end.

3 · We call you

An HMAC-signed webhook fires the moment it finishes. Fetch the video, and it's yours to publish.

Quickstart

Three requests, start to finished film. Base URL is https://cinefables.com.

1 · Get an access token

# Machine-to-machine. Tokens last 24h — cache and reuse them.
curl -X POST https://cinefables.com/oauth/token \
  -H 'Content-Type: application/json' \
  -d '{
    "grant_type": "client_credentials",
    "client_id": "cf_app_…",
    "client_secret": "cf_sec_…",
    "scope": "videos.write videos.read"
  }'

# → { "access_token": "cfat_…", "token_type": "Bearer", "expires_in": 86400 }

2 · Start a render

curl -X POST https://cinefables.com/dev/v1/videos \
  -H 'Authorization: Bearer cfat_…' \
  -H 'Content-Type: application/json' \
  -d '{
    "idea": "A street cat in Fort Kochi who guards a fishing boat at night.",
    "duration_seconds": 45,
    "language": "ml",
    "tier": "lite"
  }'

# → { "data": { "id": "a-street-cat-ms0i7mzh", "status": "queued", … } }

3 · Collect the film

# When the webhook says video.completed, fetch the outputs.
curl https://cinefables.com/dev/v1/videos/a-street-cat-ms0i7mzh \
  -H 'Authorization: Bearer cfat_…'

# → data.outputs.video_16x9   "/media/…/final-16x9.mp4?exp=…&sig=…"
#   data.outputs.video_9x16   "/media/…/final-9x16.mp4?exp=…&sig=…"
#   data.outputs.subtitles    data.outputs.thumbnail
Note — output paths are relative and signed. Prefix them with https://cinefables.com, and fetch within the signature's lifetime (at least 5 hours). Re-read the video to get a fresh link.

Authentication

OAuth 2.0. There are no API keys — every token is scoped, attributable and revocable.

Two grants

GrantUse it whenBills
client_credentials You resell generation inside your own product. Your users never see us and never need an account here. Your balance
authorization_code + PKCE You act for someone who already has a Cinefables account, with their consent. Their balance

Access tokens live 24 hours, refresh tokens 30 days and rotate on every use. Presenting a rotated refresh token revokes the whole grant — that is a theft signal, not a retry.

Scopes

ScopeGrants
videos.writeStart renders. This spends credits.
videos.readRead renders and register webhooks.
characters.readList saved characters, to reuse a face across films.
profile.readAccount details and credit balance.

Endpoints

EndpointScopeReturns
POST /dev/v1/videosvideos.writeStarts a render; returns its id.
GET /dev/v1/videos/:idvideos.readStatus and, once finished, outputs.
GET /dev/v1/videosvideos.readYour renders, paginated.
POST /dev/v1/webhooksvideos.readRegisters a callback; returns the signing secret once.
GET /dev/v1/characterscharacters.readSaved characters available for reuse.
GET /dev/v1/meprofile.readAccount and credit balance.

Render parameters

FieldTypeNotes
ideastringUp to 2000 chars. Either this or script is required.
scriptstringUp to 20000 chars. Bring your own screenplay.
duration_secondsint15–1200. Default 45. Over-long material is condensed to fit, never cut off mid-story.
languageenumen hi ml ta te kn bn mr gu. Default en.
tierenumstoryboard lite standard pro cinema realistic. Default lite. Higher tiers cost more credits.
audienceenumkids family general. Default family.
stylestringFree-form visual direction, e.g. "hand-painted watercolour".
character_idsstring[]Up to 6 saved characters to reuse — same faces across films.
no_voiceboolDrops the narrator. Characters still speak.
referencestringYour own id, echoed in the create response.

Statuses

StatusMeaning
queued / generatingIn progress.
readyFinished. Every scene rendered.
partialFinished and publishable — some scenes failed and were dropped. See failed_scenes.
failedThe render could not complete. Credits for unfinished work are refunded automatically.
blockedRefused by the content policy.

Webhooks

A render takes minutes, so don't poll — register a callback and we'll tell you. Events: video.completed and video.failed.

POST /api/your-hook                       HTTPS only
X-Cinefables-Event:     video.completed
X-Cinefables-Signature: t=1785312000,v1=9f86d081…

{
  "event": "video.completed",
  "created_at": "2026-07-28T04:21:00.000Z",
  "data": {
    "id": "a-street-cat-ms0i7mzh",
    "status": "ready",
    "title": "The Boat Cat",
    "failed_scenes": []
  }
}
The payload carries no video URL. It tells you a render settled; call GET /dev/v1/videos/:id to fetch the outputs. That keeps the callback small and means a retry always gets a fresh, unexpired link. video.completed fires for ready and partial — both are watchable films.

Verifying a delivery

Your endpoint is a public URL, so the signature is what separates us from anyone else who finds it. HMAC-SHA256 over "<timestamp>.<raw body>".

import { createHmac, timingSafeEqual } from 'node:crypto';

function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(',').map(p => p.split('=')));
  const age = Math.abs(Date.now() / 1000 - Number(parts.t));
  if (!(age < 300)) return false;   // bounds replay

  const expected = createHmac('sha256', secret)
    .update(`${parts.t}.${rawBody}`).digest('hex');
  const a = Buffer.from(expected, 'hex');
  const b = Buffer.from(parts.v1 ?? '', 'hex');
  return a.length === b.length && timingSafeEqual(a, b);
}
Use the raw bytes. Verify before your JSON middleware touches the body — a re-serialised object will not match, and the check will fail on legitimate deliveries.

Delivery behaviour

Three attempts — immediately, at 30 seconds, at 5 minutes. We retry on 5xx and network errors only: a 4xx means the body itself is wrong, and sending it again changes nothing. Return 2xx quickly and do your work afterwards. Every attempt is logged, so we can tell you exactly why a callback never arrived.

Errors, limits and credits

{ "error": "insufficient_scope",
  "error_description": "This action requires the \"videos.write\" scope" }
StatusMeans
400invalid_request — a parameter is wrong. quota_exceeded — monthly render quota reached.
401invalid_token — missing, expired or revoked. Mint a new one.
403insufficient_scope, or the application is suspended.
404No such video for you. Someone else's id is indistinguishable from one that doesn't exist.

Renders are paid for in credits from the authorising account, at the same price as the studio — there is no cheaper API path and no separate metering to reconcile. A failed render refunds the work that never ran.

Get access

The API is in private beta and applications are registered by hand, so we can talk through your use case and set a sensible quota. There is no self-serve signup yet — deliberately.

Email codezy.ai.tech@gmail.com with what you're building, the volume you expect, and your callback URL. You'll get a client id, a client secret, and a webhook signing secret.

Request access See the product