A discount on premium plans when you sign up Get your discount

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",
    "chain": true,
    "negative": ""
  },
  "scenes": [
    {
      "id": "s1", "heading": "EXT. JAZZ CLUB — NIGHT",
      "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": "h1", "html": "Rooftop night" },
    { "id": "b2", "type": "scene", "sceneId": "s1" },
    { "id": "b3", "type": "p", "html": "Rain machine on for this one." }
  ],
  "edit": { "auto": true, "clips": [], "audio": [], "titles": [], "fadeOut": 0 }
}

A scene's lines of dialogue live on the scene (dialogue: speaker as char:<id> or a free name, manner, line); every other script block is a note and never reaches an engine. Four rules the normalizer enforces so you do not have to: every scene has exactly one scene block in the script (orphans are dropped, missing ones appended), the scenes are ordered by the script, an early draft's dialogue blocks are folded into the scene above them (one written before any scene becomes a note), 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 scenes, 30 seconds per scene, 8 options per scene, 30 lines of dialogue per scene, 400 blocks, 80 clips, 40 audio clips, 40 titles, 4,000 characters per prompt or block.

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 scene, in v2: the scene's slugline, 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. Continuous shots (look.chain) are a client behaviour: the studio grabs the previous take's last frame, uploads it and passes it as the first reference. On the API, send references[0] yourself on a per-scene POST /v1/videos/generations if you want the chain; the bulk render route renders the scenes 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 three tracks with fades; titles in four 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} or POST /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.

Tagsfilms-apicinemaapivideostoryboard

Make this with the models behind the post — start with 50 free credits , or browse every engine and its price .