Node 22 batch runner for a prompt file: four lanes on Sume
Read one prompt per line, render each on Sume POST /v1/videos with four concurrent lanes and a stable idempotency key per line, and save clip-N.mp4. Node 22.

Node 22 has fetch, AbortSignal.timeout and top-level await in ES modules, so a batch runner for a prompt file needs no dependencies. The script below renders each line of a text file on Sume with four lanes pulling from a shared counter, and saves clip-N.mp4 for every success.
If your Sora-era batch script was a loop over the OpenAI Videos API, which OpenAI lists as removed on 2026-09-24, this is the same shape with Sume's submit, poll and download in the middle.
How the four lanes work
Each lane is an async function that takes the next index from a shared counter and renders that prompt. Because JavaScript is single threaded, the increment is safe without locks. Four lanes means at most four jobs in flight, which keeps you under typical workspace concurrency and the per-key rate limits; Sume answers 429 rate_limited when you exceed them.
The idempotency key is batch-N for line N. That makes a rerun of the same file return each original job, so a crash halfway costs nothing extra. If you edit a line and rerun, Sume answers 409 conflict for that key, because the body changed. Change the key prefix when you change the prompts on purpose.
| Knob | Value in the script | Why |
|---|---|---|
| Lanes | 4 | Bounds concurrent jobs |
| Poll interval | 10 s | Docs suggest about 30 s in production; 10 s is for a demo |
| Submit timeout | 60 s | Fails fast on a dead network |
| Idempotency-Key | batch-<line> | Safe reruns of the same file |
The script
Save it as batch.mjs, put one prompt per line in prompts.txt, set SUME_API_KEY, and run node batch.mjs prompts.txt. Every clip is 5 seconds at 720p in 16:9 on gemini-omni-flash-1.1. At the repo-documented $0.125 a second, 5 seconds is 0.625, billed as $0.63, so ten lines cost $6.30.
import { readFileSync, writeFileSync } from "node:fs";
const api = "https://api.sume.com/v1/videos";
const headers = { Authorization: `Bearer ${process.env.SUME_API_KEY}`, "Content-Type": "application/json" };
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const prompts = readFileSync(process.argv[2], "utf8").split("\n").filter(Boolean);
async function render(prompt, i) {
const body = { model: "gemini-omni-flash-1.1", prompt, duration: 5, resolution: "720p", aspect_ratio: "16:9" };
const res = await fetch(api, { method: "POST", signal: AbortSignal.timeout(60_000),
headers: { ...headers, "Idempotency-Key": `batch-${i}` }, body: JSON.stringify(body) });
let job = await res.json();
if (res.status !== 202) throw new Error(`${i}: ${res.status} ${job.error?.code}`);
while (["pending", "in_progress"].includes(job.status)) {
await sleep(10_000);
job = await (await fetch(job.polling_url, { headers, signal: AbortSignal.timeout(30_000) })).json();
}
if (job.status !== "completed") throw new Error(`${i}: ${job.status} ${job.error}`);
const file = await fetch(job.unsigned_urls[0], { signal: AbortSignal.timeout(120_000) });
writeFileSync(`clip-${i}.mp4`, Buffer.from(await file.arrayBuffer()));
return `clip-${i}.mp4`;
}
const LIMIT = 4;
let next = 0;
const lane = async () => { while (next < prompts.length) { const i = next++;
try { console.log("ok", await render(prompts[i], i)); } catch (e) { console.error("fail", e.message); } } };
await Promise.all(Array.from({ length: LIMIT }, lane));
What to harden
Errors in one lane are logged and the lane moves on, which is right for a prompt file where lines are independent. Collect the failures into an array and write them to failed.txt if you want a retry pass. Retry only the failed lines, and keep their keys.
Raise LIMIT slowly. A 429 means you passed your allowance, and the fix is fewer lanes, not more retries. The stored post on concurrency waves has the arithmetic for a hundred-job batch.
- Check response.ok before parsing in production code; the sample reads the error envelope on non-202 only.
- Add jitter to the poll sleep if you run many processes.
- Write results to disk as soon as each finishes, as the script does.
Cost of a batch run
Estimate the batch before you start. Each line is one 5-second job. The documented Omni 720p billed rate is $0.10 list x 1.25 = $0.125 a second, so 5 seconds is $0.625, which rounds up to $0.63. A prompt file of 40 lines is therefore 40 x 0.63 = $25.20. Check your balance first; a 402 insufficient_credits stops a lane's job at submit, and the other lanes keep running.
Sources
Related posts
More in Developers
- Node quickstart: your first Wan 3.0 video on Sume and what 30 s costs
Node 18 fetch script: POST /v1/videos with wan-3.0, poll, print the URL. The list-price arithmetic for 30 s at 480p, 720p and 1080p, times the 1.25 margin.
- Node stream.pipeline to save a Sume MP4 and catch truncation
Stream a finished Sume video to disk with stream.pipeline, then compare bytes written with content-length so a cut-off MP4 never reaches your users.
- Gemini Omni edit input is capped at 10 seconds: trim first on Sume
Google says Omni edit inputs must be 10 seconds or less. Cut the clip with Sume's video-trim at $0.02 a job, then send the short clip to the edit request.
- Gemini Omni audio is always on: generate_audio false returns 400
Sume's Omni row rejects generate_audio false with a 400 because audio is native and always on. To ship a silent clip, drop the track with video-trim after.
Written by Sume