# Render a storyboard with the eroq Films API — one call, every scene

> Create a film recipe with a look and ordered scenes, shoot every scene as an async job with one render call, poll the jobs, then publish with a cover.

Published 2026-09-08 · eroq.ai — canonical: https://eroq.ai/blog/render-a-storyboard-with-the-films-api


A single prompt makes a clip. A film is a sequence of clips that share a look and cut together, and the studio's Film mode builds one interactively — add a scene, render, add the next. The [Films API](/docs/films) is the same thing without the clicking: define the [storyboard](/glossary/storyboard) once, shoot every scene with one call, poll the jobs, publish. If you produce ten variations of the same spot for ten clients, this is the endpoint you wanted.

## A film is a recipe, never media

`POST /v1/films` stores a title and a `data` object, verbatim. The API does not interpret the recipe until you render it, and renders always land in your creations library, not in the film. Two parts:

- `look` — what every scene shares: `model`, `aspect`, `filmType`, `era`, `tempo`, `cameraType`, `lens`, `aperture`, `resolution`.
- `scenes` — an ordered array of `{ prompt, shot, seconds, cast }`. `shot` is a camera move, `cast` is a list of your character ids.

```bash
curl https://eroq.ai/v1/films \
  -H "Authorization: Bearer $EROQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Rooftop night — cut 1",
    "data": {
      "look": {
        "model": "eroq-motion-one", "aspect": "16:9",
        "filmType": "noir", "era": "1960s", "tempo": "tense",
        "cameraType": "35mm", "lens": "anamorphic", "aperture": "f1-4"
      },
      "scenes": [
        { "prompt": "A woman in a silver dress steps out onto a rooftop bar at dusk, city lights flickering on below, slow pan following her to the railing, warm practicals against the deep blue sky, wind in her hair, expectant mood.", "shot": "slow-pan", "seconds": 5 },
        { "prompt": "Close-up of her hands on the cold railing, a glass of something amber beside them, push-in as the skyline blurs behind, neon reflections crawling across the glass, quiet and tense.", "shot": "push-in", "seconds": 5 },
        { "prompt": "Wide shot from behind as she turns toward the door, a silhouette waiting there against the bar light, crane-up revealing the whole rooftop and the city beyond, patient, ominous mood.", "shot": "crane-up", "seconds": 10 }
      ]
    }
  }'
```

The response carries the film `id`. Up to 50 drafts per account; `GET /v1/films/{id}` returns the full recipe, `PATCH` updates `title` or `data`, `DELETE` removes the draft and leaves its renders in the library. The studio's Film mode saves and reloads exactly these drafts, so a recipe created by script can be opened at [/studio/video](/studio/video) and fixed by hand — or the other way around.

Write scene prompts the way the engines like them: one flowing paragraph naming subject, motion, camera, light and mood. The look supplies the rest; do not repeat "film noir, 1960s" in every scene.

## Shoot everything with one call

```bash
curl -X POST https://eroq.ai/v1/films/FILM_ID/render \
  -H "Authorization: Bearer $EROQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "store": true }'
```

Every scene with a prompt becomes an async video job — one `POST /v1/videos/generations` each, with the look's parameters merged in — and the call answers `202` with a per-scene report:

```json
{
  "id": "FILM_ID",
  "scenes": [
    { "scene": 0, "id": "job-a…", "status": "processing" },
    { "scene": 1, "id": "job-b…", "status": "processing" },
    { "scene": 2, "id": "job-c…", "status": "processing" }
  ],
  "usage": { "credits_spent": 380, "credits_remaining": 5620 }
}
```

Scenes are charged individually and queued in order, so a wallet that runs dry stops the remaining scenes cleanly instead of half-charging them. A scene that cannot be queued reports its error in place — a locked engine answers `plan_required`, for instance — while the others proceed. Any scene whose render later fails refunds itself.

Plan gates apply to the look's `model`: [Motion One](/models/eroq-motion-one) is available to every account and is the uncensored engine; Seedance, Kling, Hailuo and Veo are SFW only and need the plan that unlocks them. `seconds` per scene is capped by the plan too (10, 15, 20 or 30 s).

## Poll the jobs

Each queued scene is an ordinary video job. Poll `GET /v1/videos/generations/{id}` every few seconds — free — until `status` is `succeeded` or `failed`.

```js
const BASE = 'https://eroq.ai/v1'
const headers = { Authorization: `Bearer ${process.env.EROQ_API_KEY}` }

async function renderFilm(filmId) {
  const report = await fetch(`${BASE}/films/${filmId}/render`, {
    method: 'POST',
    headers: { ...headers, 'Content-Type': 'application/json' },
    body: JSON.stringify({ store: true }),
  }).then(r => r.json())

  const segments = []
  for (const scene of report.scenes.filter(s => s.status === 'processing')) {
    let job
    do {
      await new Promise(r => setTimeout(r, 4000))
      job = await fetch(`${BASE}/videos/generations/${scene.id}`, { headers }).then(r => r.json())
    } while (job.status === 'processing')
    if (job.status === 'succeeded') segments[scene.scene] = job.data[0].url
  }
  return segments.filter(Boolean)
}
```

Polling sequentially is fine at three scenes; at thirty, poll in parallel or subscribe to the `video.generation.succeeded` webhook and collect URLs as they arrive. Either way you end with ordered clip URLs, which is exactly what publishing wants.

## Publish with a cover

Two calls. The cover first — multipart, an image up to 5 MB, free:

```bash
curl -X POST https://eroq.ai/v1/films/FILM_ID/cover \
  -H "Authorization: Bearer $EROQ_API_KEY" \
  -F "file=@cover.jpg"
```

Then the film itself:

```bash
curl -X POST https://eroq.ai/v1/films/FILM_ID/publish \
  -H "Authorization: Bearer $EROQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Rooftop night",
    "segments": ["https://…/scene-1.mp4", "https://…/scene-2.mp4", "https://…/scene-3.mp4"],
    "durationSeconds": 20
  }'
```

`segments` is 1 to 24 ordered URLs, and every one must be a hosted render from your own library — foreign media is refused. The film appears on the [Community](/community) shelf as one entry that plays the segments back to back, with the cover on the card and in the player. Viewers see the title, the cover, the clips and the counts; prompts, look and cast stay private. `DELETE /v1/films/{id}/publish` takes it down and keeps likes and comments for a re-publish. Deleting the draft deletes the community entry too — the scene renders stay in the library.

## Batches, for agencies

The pattern that scales: one recipe per client as a template, a loop that swaps the variables, and a workspace so the whole team draws on one wallet.

- **Variants are recipes.** The same three scenes at `16:9` for YouTube and `9:16` for Reels are two films with two looks. Create, render, poll, deliver — no human in the loop until review.
- **Cost is arithmetic.** A three-scene Motion One film at 5, 5 and 10 seconds is 380 credits, about $3.80 at the entry pack rate; the Studio plan's 20,000 monthly credits cover roughly fifty of them, with 4× rate limits and 15 seats. Takes multiply the number, so decide how many alternates the review actually needs.
- **Throughput is per key.** Video requests are rate-limited per key (6 a minute on the base tier, multiplied by the plan). Give each pipeline its own key so one client's batch never throttles another's.
- **Characters travel.** Put a client's recurring presenter in `cast` and the same face holds across every scene through image-to-video — see [agencies](/use-cases/agencies) for the wider workflow.

## FAQ

### What happens when one scene fails?

Nothing to the others. The failed scene refunds itself; re-render it alone with `POST /v1/videos/generations` and the same look parameters, then splice its URL into `segments`. Calling `render` on the film again would re-shoot and re-charge every scene.

### Can I render a film someone built in the studio?

Yes. `GET /v1/films` lists the drafts the studio's Film mode saved; render any of them by id.

### Does publishing cost credits?

No. The cover upload and the publish call are free; you paid for the scene renders and that is all.

Start with a three-scene recipe on the free credits — [get an API key](/signup) and shoot it.
