Bulk image generation: script hundreds of AI images via API

Send one image request per row, each with its own Idempotency-Key, async mode, and up to four images per call. Your plan's concurrency sets the pace.

5 min readSume
All posts

Bulk image generation from a script is a loop: one image request per row of your list, each row with its own prompt. On Sume, send each row to POST /v1/images with its own Idempotency-Key and mode: "async", ask for up to four images per call, and collect the results as the jobs finish. Your workspace's concurrency limit decides how many run at once, and the rest wait in a queue.

The Sume facts below come from the Image API, Generation admission, and Jobs and results docs, read on 2026-09-27.

How do I generate images in bulk from a list of prompts?

prompt is one string per request, so each row is its own call. For every row:

  • Derive the Idempotency-Key from the row, such as catalog-v1-sku-4411. Retrying with the same key and body returns the original job instead of billing a second one; the same key with a different payload is 409 idempotency_conflict.
  • Send mode: "async". The submit answers 202 at once with the job envelope, instead of holding the request open for up to 30 seconds.
  • Store the job id next to the row. Poll GET /v1/jobs/{id}/status with backoff, then fetch GET /v1/jobs/{id}/result for the images, or send mode: "webhook" with a webhook_url and take one terminal callback per job.
  • Branch on the status code, not the body shape: 200 is the image response, 202 is the job envelope.
  • Download the files you keep. Image API result URLs are Sume-hosted and signed.
import os, requests

API = "https://api.sume.com/v1/images"
KEY = os.environ["SUME_API_KEY"]
rows = [
    {"id": "sku-4411", "prompt": "a matte black bottle on marble, studio light"},
    {"id": "sku-4412", "prompt": "a jar of honey on an oak table, soft light"},
]

jobs, done = {}, {}
for row in rows:
    r = requests.post(
        API,
        headers={"Authorization": f"Bearer {KEY}",
                 "Idempotency-Key": f"catalog-v1-{row['id']}"},
        json={"model": "bytedance-seed/seedream-4.5", "prompt": row["prompt"],
              "n": 2, "mode": "async"},
        timeout=60,
    )
    r.raise_for_status()
    body = r.json()
    if r.status_code == 200:  # the image response
        done[row["id"]] = [image["url"] for image in body["data"]]
    else:  # 202: the job envelope
        jobs[row["id"]] = body["data"]["job"]["id"]

How many images can one request return?

n sets the images per request. The docs allow up to 10 per call and say each model's ceiling is lower; in current code it is 4 on most models and 1 on Grok Imagine (x-ai/grok-image). A large n is also one of the configurations most likely to run past the 30-second wait, which is one more reason to submit async. See 4K, quality, and image count for each model's settings.

How many image jobs can run at once?

As many as your workspace's generation concurrency limit, which your plan sets. In current code each POST /v1/images creates an image_generation job, and paid generation jobs are admitted queue-first: jobs past the limit wait as queued, and a submit fails with 429 queue_full only when the queue is full too. The per-plan numbers and the full admission rules are in video job concurrency and queueing. For a batch script:

  • Size each wave from the live generation_limits snapshot, which generation submit responses include when Sume can compute it: max(0, concurrency_limit - active_generation_jobs - queued_generation_jobs), capped by queue_capacity_remaining, is the budget for new in-flight work.
  • Reads and writes have separate per-minute budgets, so polling does not use up the budget your submits need. Back off on retry-after.

What happens when a request fails or my script stops?

Rerun the script. Rows already submitted get their original job back under the same key, and nothing is billed twice. A failed or cancelled generation is not billed, and the original job keeps answering its key, so give a failed row a new key, such as a bumped version, to make it again. The submit itself tells you what happened to each row:

Submit responses, from Generation admission and the Image API docs, read 2026-09-27.
ResponseWhat it means for the row
202 with data.job.idAccepted. Store the id and poll it, or wait for the webhook.
400 unsupported_parameterThe model does not list a field you sent. Fix the body.
402 insufficient_creditsSume cannot reserve the estimated cost from your balance.
409 idempotency_conflictThe key was already used with a different body.
429 queue_fullNo accepted-job capacity is left. Stop adding work and poll existing jobs until one finishes.
429 rate_limitedToo many requests. Back off, using retry-after when present.

What does a bulk run cost?

A batch costs the per-image price × n × rows, plus a 5.5% agent fee by default; on ChatGPT Image models the per-image price also depends on size and quality. Sume reserves each job's estimate when it accepts the submit, so the balance has to cover queued jobs too, and a failed job's reservation is released or refunded. Per-model prices are in AI image generator API cost.

If each image comes from a saved Format rather than a single model call, one Format bulk request queues 1 to 100 runs; see Format bulk runs.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume