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 accessRendering a film takes minutes, not milliseconds. The API is built around that: you submit, we call you back.
POST an idea or a full script. You get a video id back immediately — the request never blocks on the render.
Story, cast with consistent faces, voices, score, subtitles, and the final cut — assembled end to end.
An HMAC-signed webhook fires the moment it finishes. Fetch the video, and it's yours to publish.
Three requests, start to finished film. Base URL is https://cinefables.com.
# 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 }
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", … } }
# 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
https://cinefables.com, and fetch within the signature's
lifetime (at least 5 hours). Re-read the video to get a fresh link.
OAuth 2.0. There are no API keys — every token is scoped, attributable and revocable.
| Grant | Use it when | Bills |
|---|---|---|
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.
| Scope | Grants |
|---|---|
videos.write | Start renders. This spends credits. |
videos.read | Read renders and register webhooks. |
characters.read | List saved characters, to reuse a face across films. |
profile.read | Account details and credit balance. |
| Endpoint | Scope | Returns |
|---|---|---|
POST /dev/v1/videos | videos.write | Starts a render; returns its id. |
GET /dev/v1/videos/:id | videos.read | Status and, once finished, outputs. |
GET /dev/v1/videos | videos.read | Your renders, paginated. |
POST /dev/v1/webhooks | videos.read | Registers a callback; returns the signing secret once. |
GET /dev/v1/characters | characters.read | Saved characters available for reuse. |
GET /dev/v1/me | profile.read | Account and credit balance. |
| Field | Type | Notes |
|---|---|---|
idea | string | Up to 2000 chars. Either this or script is required. |
script | string | Up to 20000 chars. Bring your own screenplay. |
duration_seconds | int | 15–1200. Default 45. Over-long material is condensed to fit, never cut off mid-story. |
language | enum | en hi ml ta te kn bn mr gu. Default en. |
tier | enum | storyboard lite standard pro cinema realistic. Default lite. Higher tiers cost more credits. |
audience | enum | kids family general. Default family. |
style | string | Free-form visual direction, e.g. "hand-painted watercolour". |
character_ids | string[] | Up to 6 saved characters to reuse — same faces across films. |
no_voice | bool | Drops the narrator. Characters still speak. |
reference | string | Your own id, echoed in the create response. |
| Status | Meaning |
|---|---|
queued / generating | In progress. |
ready | Finished. Every scene rendered. |
partial | Finished and publishable — some scenes failed and were dropped. See failed_scenes. |
failed | The render could not complete. Credits for unfinished work are refunded automatically. |
blocked | Refused by the content policy. |
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": []
}
}
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.
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);
}
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.
{ "error": "insufficient_scope",
"error_description": "This action requires the \"videos.write\" scope" }
| Status | Means |
|---|---|
400 | invalid_request — a parameter is wrong. quota_exceeded — monthly render quota reached. |
401 | invalid_token — missing, expired or revoked. Mint a new one. |
403 | insufficient_scope, or the application is suspended. |
404 | No 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.
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