60 video jobs in Node with a promise pool: which plans need waves

A Node promise pool for Sume video jobs, plus the fit table: 60 jobs against accepted capacity on Free, Pro, Startup and Scale, and how many waves each needs.

3 min readSume
All posts

Sixty jobs fit in a single burst only on Scale, whose accepted capacity is 120. Startup accepts 48, Pro 24 and Free 6, so on those plans a 60-job batch must go out in waves or it meets 429 queue_full. A promise pool with a width equal to the processing cap is the Node way to do that.

The cost is the same on every plan: sixty 10-second Wan 3.0 clips at 480p total $37.50 on Sume.

Fit table

Waves means the number of rounds if you only keep one processing width in flight.

Docs capacity table, read 2026-10-08
PlanWidthAccepted capacity60 in one burst?Waves at width
Free16No, queue_full60
Pro424No, queue_full15
Startup848No, queue_full8
Scale20120Yes3

The pool

Workers pull the next index until the list is empty, which keeps exactly width jobs open.

const API = "https://api.sume.com";
const H = { Authorization: "Bearer " + process.env.SUME_API_KEY, "Content-Type": "application/json" }; const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function one(i, prompt) {
  const res = await fetch(API + "/v1/videos", {
    method: "POST",
    headers: { ...H, "Idempotency-Key": "pool-" + String(i).padStart(3, "0") },
    body: JSON.stringify({ model: "wan-3.0", prompt, duration: 10, resolution: "480p" }),
  });
  if (!res.ok) throw new Error("submit " + i + " -> " + res.status);
  const { id } = await res.json();
  for (;;) {
    const r = await fetch(API + "/v1/jobs/" + id + "/status", { headers: H });
    const s = (await r.json()).data;
    if (s.terminal) return { i, id, status: s.sume_status };
    await sleep((s.next_poll_after_seconds ?? 5) * 1000);
  }
}

async function pool(items, width, fn) {
  const out = [];
  let next = 0;
  const worker = async () => {
    while (next < items.length) { const i = next++; out[i] = await fn(i, items[i]); }
  };
  await Promise.all(Array.from({ length: width }, worker));
  return out;
}
const prompts = Array.from({ length: 60 }, (_, n) => "Lantern festival, shot " + n);
console.log(await pool(prompts, 8, one));

Notes

The width of 8 is Startup's processing cap. Replace it with concurrency_limit from your own generation_limits. Run the file as an ES module on Node 18 or later.

Why a pool and not Promise.all

Promise.all over 60 submits would send all 60 at once. On a plan with less accepted capacity, the tail of the burst gets 429 queue_full. A pool of fixed width keeps the number of open jobs near the processing cap, so queue slots stay free and the error never appears.

  • Width 1 to 4 suits Free and Pro; 8 suits Startup; 20 suits Scale.
  • Prefer the effective concurrency_limit from generation_limits over the table value.

The pool returns results in input order because each worker writes to out[i], so you can zip the output back onto your source rows without sorting.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume