Build a contact sheet from one image call with n variants in Python
Ask the Image API for several variants with n, then tile them into one sheet with Pillow so a reviewer can pick in one glance. Handles 200 versus 202.

To review several ad images at once, send one POST /v1/images with n set to the number of variants, download the returned URLs, and paste them onto one canvas with Pillow. The script below does that in about twenty lines and saves a single JPEG you can open or send to a reviewer.
The Image API docs say n accepts 1 to 10 per call, but each model has its own ceiling, so read the n range from the catalog before you pick a number.
Why use n instead of several calls?
One request is one place to read the cost and one set of URLs to name. The response carries usage.cost, the amount billed to your wallet in USD, so a four-image sheet is one number to log. Separate calls give you separate failures to track.
What does the script look like?
It checks the status code first. A 200 is the image response; a 202 means the generation outlived the 30-second wait and returned a job envelope, which you read from the job result endpoint instead.
import io, os, requests
from PIL import Image, ImageOps
r = requests.post(
"https://api.sume.com/v1/images",
headers={"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"},
json={"model": "openai/gpt-image-2", "n": 4,
"prompt": "Studio photo of a stainless water bottle on a desk"},
timeout=60,
)
if r.status_code != 200:
raise SystemExit(f"{r.status_code}: poll the job instead: {r.text[:200]}")
body = r.json()
tiles = []
for item in body["data"]:
raw = requests.get(item["url"], timeout=60).content
img = Image.open(io.BytesIO(raw)).convert("RGB")
tiles.append(ImageOps.pad(img, (400, 400), color="white"))
sheet = Image.new("RGB", (400 * len(tiles), 400), "white")
for i, tile in enumerate(tiles):
sheet.paste(tile, (i * 400, 0))
sheet.save("sheet.jpg", quality=88)
print(len(tiles), "tiles, billed", body["usage"]["cost"])What happens on a 202?
The job id sits at data.job.id. Poll GET /v1/jobs/{id}/status, then fetch GET /v1/jobs/{id}/result for the images, as the jobs docs describe. Slow settings, such as 4K, high quality or a large n, are the ones most likely to take that path.
How do I choose the winner?
Number the tiles in the same order as data, write the pick next to the job id, and keep the sheet with the record. That way the winning URL is traceable later.
Sources
Related posts
More in Developers
- Does the Sume SDK retry POST? Only with an Idempotency-Key
createSumeClient retries 408, 429 and 5xx twice with backoff, but a POST is only retried when it carries an Idempotency-Key. Why and how to set it.
- curl --retry on a POST: retry a Sume submit with one key
curl --retry also retries a POST, and it resends the same headers each time. Put an Idempotency-Key on a Sume submit first, then pick --retry-max-time.
- Cut AI clips to the beat: BPM to Timeline start times in Python
Ask Lyria for a tempo, turn it into cut times, and render the clips on the beat with Sume Timeline. Python sketch, cost, and how to check the tempo.
- Decart lucy-latest vs a pinned Lucy model; Sume catalog ids
Decart's lucy-latest alias can move while legacy Lucy Clip costs $0.15 per second against $0.04 for Lucy 2.5. Why pin a model id, and how to do it on Sume.
Written by Sume