Batch 100 product videos with JavaScript / Python

Queue SKUs, respect the window

There is no hidden batch endpoint. You own the loop. The API tells you when to wait. That is enough to render a catalog overnight.

The constraint that matters

15 jobs / 30 minutes

Per API key. GET status is not limited. On 429, wait Retry-After seconds.

100 product clips ≈ 7 windows ≈ 3.5 hours. 1,000 clips ≈ 1.4 days of wall time if you run continuously. The €59.99 plan does not add extra per-clip fees.

Cost of a catalog run

The REST API is a flat €59.99 / month (API / Build plan). Generations are not billed per clip. Throughput is capped at 15 requests per 30 minutes per API key. MCP uses studio credits and does not require this plan.

155,000

Effective cost / video

€0.20

Plan (flat)

€59.99

Time at rate limit

10 h

Max throughput

720 / day

Example: 100 product videos need 7 windows of 15 jobs, about 3.5 hours if you respect Retry-After. See batch generation and rate-limit docs.

JavaScript (Node 18+)

batch.mjs
const KEY = process.env.NB_API_KEY;
const skus = [
  { prompt: "Red running shoe on white cyclorama, slow orbit" },
  { prompt: "Matte water bottle, condensation, studio light" },
  // ... up to 100
];

async function generate(prompt) {
  for (;;) {
    const res = await fetch("https://nanobananavideo.com/api/v1/text-to-video.php", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        prompt,
        video_model: "seedance2",
        resolution: "720p",
        duration: 5,
        aspect_ratio: "1:1",
      }),
    });
    if (res.status === 429) {
      const wait = Number(res.headers.get("Retry-After") || 60);
      await new Promise((r) => setTimeout(r, wait * 1000));
      continue;
    }
    const json = await res.json();
    if (!res.ok || !json.success) throw new Error(json.error || res.statusText);
    return json.video_url;
  }
}

const out = [];
for (const sku of skus) {
  out.push({ prompt: sku.prompt, url: await generate(sku.prompt) });
  console.log("done", out.length, "/", skus.length);
}
console.log(JSON.stringify(out, null, 2));

Python

batch.py
import json, os, time, urllib.request

KEY = os.environ["NB_API_KEY"]
SKUS = [
    "Red running shoe on white cyclorama, slow orbit",
    "Matte water bottle, condensation, studio light",
]

def generate(prompt: str) -> str:
    body = json.dumps({
        "prompt": prompt,
        "video_model": "seedance2",
        "resolution": "720p",
        "duration": 5,
        "aspect_ratio": "1:1",
    }).encode()
    while True:
        req = urllib.request.Request(
            "https://nanobananavideo.com/api/v1/text-to-video.php",
            data=body,
            headers={
                "Authorization": f"Bearer {KEY}",
                "Content-Type": "application/json",
            },
            method="POST",
        )
        try:
            with urllib.request.urlopen(req) as res:
                data = json.loads(res.read().decode())
                return data["video_url"]
        except urllib.error.HTTPError as e:
            if e.code == 429:
                wait = int(e.headers.get("Retry-After") or 60)
                time.sleep(wait)
                continue
            raise

for i, prompt in enumerate(SKUS, 1):
    url = generate(prompt)
    print(i, url)

Headers and 429 JSON: rate limits. Error map: errors.