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.

5 min readSume
All posts

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.

Batch knobs and their effect (Sume docs, read 2026-10-05)
KnobValue in the scriptWhy
Lanes4Bounds concurrent jobs
Poll interval10 sDocs suggest about 30 s in production; 10 s is for a demo
Submit timeout60 sFails fast on a dead network
Idempotency-Keybatch-<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

All Developers posts

Written by Sume