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.

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-Keyfrom the row, such ascatalog-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 is409 idempotency_conflict. - Send
mode: "async". The submit answers202at 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}/statuswith backoff, then fetchGET /v1/jobs/{id}/resultfor the images, or sendmode: "webhook"with awebhook_urland take one terminal callback per job. - Branch on the status code, not the body shape:
200is the image response,202is 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_limitssnapshot, which generation submit responses include when Sume can compute it:max(0, concurrency_limit - active_generation_jobs - queued_generation_jobs), capped byqueue_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:
| Response | What it means for the row |
|---|---|
202 with data.job.id | Accepted. Store the id and poll it, or wait for the webhook. |
400 unsupported_parameter | The model does not list a field you sent. Fix the body. |
402 insufficient_credits | Sume cannot reserve the estimated cost from your balance. |
409 idempotency_conflict | The key was already used with a different body. |
429 queue_full | No accepted-job capacity is left. Stop adding work and poll existing jobs until one finishes. |
429 rate_limited | Too 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
- Can multiple people use the same API key?
They can, but they then share its rate limit, usage record, and revocation. Give each person or service its own key, and know what stays shared.
- Create a talking avatar using Python with the Sume API
Create a talking avatar in Python with Requests: generate the avatar, poll its job, send it a script to speak, then read the finished video's URL.
- Do AI-generated video URLs expire? How Sume stores outputs
Not for Format runs and Agent Completions: Sume returns their media on media.sume.com URLs that do not expire, and anyone holding a link can open it.
- Download a generated video from the Sume API: 401s and 302s
Sume unsigned_urls need your API key and answer with a 302 redirect. Download the MP4 with curl -L or code, and fix each 401, 404, or 409.
Written by Sume