blog/developers·Sep 24, 2026·6 min·by the eroq team
Cinema projects through the films API — script, storyboard, render, media, export
The scripted side of Cinema. What a v2 film project looks like on /v1/films, how the render route reads the script, how media is re-signed, how a cut is saved with the export route and published.
Everything the Cinema workspace does goes through /v1/films, and the studio is a client of it like any other. This guide is for the developer who wants to build or automate films: the project format the routes keep opaque, the render call that reads the script, the media map, the export route, and the caps. It assumes you have read the storyboard-rendering guide for the v1 basics; this one is what v2 adds.
The project
A film row has a title, a cover_url, a published_creation_id and a data document. data is the whole project — the routes validate its size (400 KB) and normalize it on read, and otherwise store what you send. The v2 shape:
{
"v": 2,
"look": {
"model": "seedance-2-0-mini",
"aspect": "16:9",
"resolution": "720p",
"audio": true,
"seed": 4242,
"filmType": "noir", "era": "1960s", "tempo": "tense",
"cameraType": "35mm film", "lens": "anamorphic", "aperture": "f/1.4",
"palette": "neon noir", "lighting": "practicals",
"negative": ""
},
"notes": "Rooftop night. Rain machine on.",
"sequences": [
{ "id": "q1", "heading": "EXT. JAZZ CLUB — NIGHT", "cast": ["char:9f3…"], "look": { "lighting": "practicals" } }
],
"scenes": [
{
"id": "s1", "sequence": "q1", "heading": "",
"prompt": "A detective in a rain-soaked trench coat waits under a flickering neon sign, slow push-in.",
"shot": "push-in", "seconds": 5, "cast": ["char:9f3…"], "takes": 1,
"dialogue": [{ "id": "l1", "speaker": "char:9f3…", "manner": "whispering", "line": "It's still lit." }],
"options": [{ "libraryId": "cr_…", "contentType": "video/mp4", "seconds": 5 }],
"pick": 0
}
],
"script": [
{ "id": "b1", "type": "scene", "sceneId": "s1" }
],
"edit": { "auto": true, "clips": [], "audio": [], "titles": [], "fadeOut": 0 }
}
Since 13 October the studio shows this as the Story: each entry of scenes is a shot, and sequences are the Story's scenes (one place and time in heading, a cast and an optional look that overrides the film's light, palette, lens, aperture, camera or tempo), each shot pointing at its own with sequence. A shot's lines of dialogue live on the shot (dialogue: speaker as char:<id> or a free name, manner, line); notes on the film, a scene or a shot never reach an engine, and a shot's link (continue or angle) says how it follows the one before. Four rules the normalizer enforces so you do not have to: script keeps one scene block per shot and sets their order (old Notion-style text blocks are folded into notes), a shot's legacy heading moves to its scene, an old look.chain becomes links and is cleared, and every libraryId in options is a creation id — media is never stored in the project, only pointed at. A v1 draft (look + scenes with prompt/shot/seconds/cast/takes) normalizes into this shape on read, so old integrations keep working.
Caps: 24 shots, 30 seconds per shot, 8 options per shot, 30 lines of dialogue per shot, 80 clips, 40 audio clips, 120 titles, 4,000 characters per prompt or note.
Create, read, update
POST /v1/films
{ "title": "Rooftop night", "data": { … } }
GET /v1/films → cards: scenes, rendered, seconds, aspect, cover, preview
GET /v1/films/{id} → the row + data + media
PATCH /v1/films/{id} → title and/or data (whole document)
DELETE /v1/films/{id} → the draft; renders stay in the library
GET /v1/films/{id} returns a media map next to the project: every take, clip and sound the project points at, keyed creation:<id> or upload:<id>, with a fresh signed URL, content type, length and thumbnail. Your storage is private, so URLs expire; the map is how a client gets working links without owning a key per file. To refresh a subset — before a signed URL lapses, or when a new file joins the bin — POST /v1/films/media with { "keys": ["creation:…", "upload:…"] } (1 to 300) returns the same shape for those keys. Unknown or foreign keys are simply absent.
PATCH replaces data whole. Read, modify, write; there is no partial merge, and a stale write wins over a newer one, so serialize your writers.
Render
POST /v1/films/{id}/render
{ "store": false }
One call queues every scene that has a prompt, as one async video job per take, on the look's engine — the same pipeline as POST /v1/videos/generations, each charged individually. The response maps each scene id to its job ids, or to the error that kept it from queueing; scenes fail independently, and a charged failure refunds itself. Poll the jobs like any render.
What the route composes per shot, in v2: its scene's slugline and cast, its action and its dialogue lines (Mina (whispering) says: "…"), with the speakers' character sheets and voices joining the references — the same prompt the studio sends. The look rides every scene, including audio and seed. The engine's caps clamp format, resolution, length and references before billing; a scene that asks for a feature the engine lacks is refused, not silently reduced.
When a take lands, write it into the scene's options with its libraryId and set pick; the studio does this for you, an integration does it after polling. Links between shots (link) are a client behaviour: for Continue the shot the studio grabs the previous take's last frame, uploads it and passes it as the start frame; for Same scene, new angle it passes it as a reference. On the API, send references[0] yourself on a per-shot POST /v1/videos/generations if you want the chain; the bulk render route renders the shots as written.
The edit and the export
The edit document is pure data — clips referencing { "kind": "scene", "sceneId" }, creation or upload, with in/out trims, transitions, volume and fit; audio clips on up to eight tracks with fades; titles in twelve styles; a fade-out. The studio renders it to an MP4 in the browser; the API does not render edits server-side.
What the API does is keep the result:
POST /v1/films/{id}/export?seconds=42.5&signature=<edit signature>
Content-Type: video/mp4
<raw MP4 bytes>
The body is the file, streamed. It lands in the library as a video creation — so it plays, downloads and publishes like any clip — and counts against the workspace's storage like every library file. Free. The signature is a hash of the edit at export time; the film records it, and a later change to the edit makes the saved cut stale. A re-export replaces the previous cut when that one was never published and nothing in the edit uses it.
Rate-limited with uploads; a full bucket refuses before anything is read.
Publish
POST /v1/films/{id}/cover (multipart file, image ≤ 5 MB, free)
POST /v1/films/{id}/publish
{ "title": "Rooftop night", "segments": ["<the saved cut's URL>"], "durationSeconds": 42 }
A film with a saved cut publishes the cut as its single segment; a film without one publishes its picked takes in order. Every segment must be a video in your library. Publication goes through review (review starts at pending on GET /v1/films/{id}); DELETE /v1/films/{id}/publish takes it down and keeps the engagement.
What the API does not do
- Render the edit. The compositor is in the studio; the API stores the result.
- Grab last frames. Chain by sending
references[0]per scene. - Serve raw storage URLs. Everything is signed, through
GET /v1/films/{id}orPOST /v1/films/media.
FAQ
Is the project format documented?
The routes treat data as opaque JSON under a size cap and normalize it on read. The shape above is what the studio writes; the fields are stable, additions are optional.
Does the render route charge for scenes that already have takes?
It renders every scene with a prompt, takes included. To re-render one scene, call POST /v1/videos/generations for that scene and write the result into its options.
Can I upload my own footage into a film?
Upload it with POST /v1/uploads and reference it from the edit as { "kind": "upload", "id": … }. The media map signs it like any take.
Why is store an option on render?
store: true persists every clip to your storage at the Store's rate (2 credits per 10 MB); the default keeps the free library copy. Cinema's takes live in the library either way.
Read the films reference for every field, and the storyboard guide for the v1 walk-through.
Make it in eroq
Build it on the API
An API key takes a minute. The same engines, the same prices, async jobs and webhooks.
40 free credits · no card · private mode on every account
