blog/developers·Sep 8, 2026·6 min·by the eroq team
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.
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 is the same thing without the clicking: define the 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 }.shotis a camera move,castis a list of your character ids.
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 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
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:
{
"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 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.
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:
curl -X POST https://eroq.ai/v1/films/FILM_ID/cover \
-H "Authorization: Bearer $EROQ_API_KEY" \
-F "[email protected]"
Then the film itself:
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 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:9for YouTube and9:16for 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
castand the same face holds across every scene through image-to-video — see 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 and shoot it.
Make this with the models behind the post — start with 50 free credits , or browse every engine and its price .