Batch Image Generation via API — How to Scale to Thousands of Images
You've tested your image generation endpoint: it works for one request. Now you need to generate 3,000 product images for an e-commerce catalog by Friday. Running them sequentially — one request, wait, next request — will take five to six hours and fail halfway through if the network hiccups. Doing it right means concurrent requests, per-item retry logic, and cost control before you fire the first call.
This guide covers the full pattern using the GenRelay API: authentication, concurrency with Python asyncio, retry logic, model selection by volume, and cost estimates for real batch sizes.
What does "batch image generation" mean via API?
Batch image generation means sending multiple generation requests in parallel — controlled concurrency — rather than one at a time, collecting results as they arrive, and retrying only the failures. GenRelay's image generation endpoint is synchronous and stateless: each POST returns an image URL directly in the response body, so no polling loop is required. This makes concurrency straightforward.
As of August 2026, GenRelay offers three image models in batch workloads:
| Model | 1K resolution | 2K resolution | 4K resolution |
|---|---|---|---|
| Nano Banana Pro | $0.030 / image | $0.030 / image | $0.042 / image |
| Nano Banana 2 | $0.020 / image | — | $0.036 / image |
| GPT-image-2 | $0.014 / image | — | — |
Model choice drives the majority of your batch cost. More on that below.
How do I authenticate with the GenRelay image API?
Authentication uses a single bearer token in every request header. Set it once as an environment variable and pass it to all requests:
import os
API_KEY = os.environ["GENRELAY_API_KEY"]
BASE_URL = "https://genrelay.ai/v1/images/generations"
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
Generate a key at genrelay.ai → Dashboard → API Keys. There is no per-key rate limit hard-coded for batch workloads on paid plans, but you should cap concurrency on your end to stay within the burst window.
How do I run concurrent image requests in Python?
Use asyncio with aiohttp to fire multiple requests in parallel, controlled by a semaphore:
import asyncio
import aiohttp
import os
API_KEY = os.environ["GENRELAY_API_KEY"]
BASE_URL = "https://genrelay.ai/v1/images/generations"
async def generate_one(session, semaphore, prompt, model="nano-banana-pro", size="1024x1024"):
async with semaphore:
payload = {
"model": model,
"prompt": prompt,
"size": size,
"n": 1,
}
async with session.post(
BASE_URL,
headers={"Authorization": f"Bearer {API_KEY}"},
json=payload,
timeout=aiohttp.ClientTimeout(total=60),
) as resp:
resp.raise_for_status()
data = await resp.json()
return data["data"][0]["url"]
async def batch_generate(prompts, model="nano-banana-pro", size="1024x1024", concurrency=20):
semaphore = asyncio.Semaphore(concurrency)
async with aiohttp.ClientSession() as session:
tasks = [
generate_one(session, semaphore, prompt, model, size)
for prompt in prompts
]
return await asyncio.gather(*tasks, return_exceptions=True)
# Run
prompts = [
"product shot of a ceramic mug, white background, studio lighting",
"top-down view of a wooden cutting board, natural light",
# ... add thousands more
]
results = asyncio.run(batch_generate(prompts, concurrency=20))
concurrency=20 is a safe starting point for most accounts. You can increase to 50 for high-throughput workloads on paid plans. If you see intermittent 429 responses, lower concurrency or add backoff between request bursts.
How do I handle errors and retries in a batch?
Network errors, occasional model timeouts, and burst-limit responses are normal at scale. Wrap every request with a retry loop using exponential backoff:
import asyncio
import aiohttp
import os
async def generate_with_retry(session, semaphore, prompt, model, size, max_retries=3):
for attempt in range(max_retries):
try:
async with semaphore:
payload = {"model": model, "prompt": prompt, "size": size, "n": 1}
async with session.post(
"https://genrelay.ai/v1/images/generations",
headers={"Authorization": f"Bearer {os.environ['GENRELAY_API_KEY']}"},
json=payload,
timeout=aiohttp.ClientTimeout(total=60),
) as resp:
if resp.status == 429:
await asyncio.sleep(2 ** attempt)
continue
resp.raise_for_status()
data = await resp.json()
return {"prompt": prompt, "url": data["data"][0]["url"], "error": None}
except Exception as exc:
if attempt == max_retries - 1:
return {"prompt": prompt, "url": None, "error": str(exc)}
await asyncio.sleep(2 ** attempt)
async def batch_with_retry(prompts, model="nano-banana-pro", size="1024x1024", concurrency=20):
semaphore = asyncio.Semaphore(concurrency)
async with aiohttp.ClientSession() as session:
tasks = [
generate_with_retry(session, semaphore, p, model, size)
for p in prompts
]
return await asyncio.gather(*tasks)
Exponential backoff (2^attempt seconds: 1s, 2s, 4s) handles 429s and transient failures without hammering the API on a spike.
Which model should I choose for a large batch?
Model selection is the single biggest cost lever in a batch workload.
GPT-image-2 at $0.014/image is the lowest unit cost on GenRelay. Use it when generation volume is high and resolution requirements are flexible — social media assets, thumbnails, variant previews where you'll evaluate and upscale selectively. See the GPT-image-2 guide for parameter details.
Nano Banana 2 at $0.020/image (1K) delivers stronger fidelity than GPT-image-2 at a moderate cost premium. A good fit for product thumbnails, preview images, and any context where detail accuracy matters more than raw throughput.
Nano Banana Pro at $0.030/image (1K–2K) is the quality tier — better textures, accurate fine details, closer prompt adherence. Use it for hero images, catalog shots, and images displayed at large sizes. The Nano Banana Pro overview covers its full parameter set.
Cost comparison for a 3,000-image batch at 1K resolution:
| Model | Per-image cost | 3,000 images |
|---|---|---|
| GPT-image-2 | $0.014 | $42.00 |
| Nano Banana 2 | $0.020 | $60.00 |
| Nano Banana Pro | $0.030 | $90.00 |
| Nano Banana Pro (4K) | $0.042 | $126.00 |
A common pattern for catalog workloads: run the full batch at Nano Banana 2 pricing, then re-run only the hero SKUs at Nano Banana Pro. Net cost versus running everything at the higher tier can drop 30–40%.
How do I track progress and collect failures for requeue?
Write results to JSONL as they complete so a crash mid-batch doesn't lose completed work:
import asyncio, json
async def run_and_save(prompts, output_path, **kwargs):
results = await batch_with_retry(prompts, **kwargs)
failures = []
with open(output_path, "w") as f:
for result in results:
f.write(json.dumps(result) + "\n")
if result.get("error"):
failures.append(result["prompt"])
print(f"Completed: {len(results) - len(failures)} ok, {len(failures)} failed")
return failures
# First pass
failed = asyncio.run(run_and_save(all_prompts, "batch_pass1.jsonl", concurrency=20))
# Retry pass with lower concurrency for stability
if failed:
asyncio.run(run_and_save(failed, "batch_pass1_retry.jsonl", concurrency=5))
JSONL (one JSON object per line) is easy to stream, append to, and postprocess with jq or Pandas.
How do I download and store images from batch results?
GenRelay image URLs are temporary. For durable storage, download images immediately after generation and push to your own object store:
import asyncio
import httpx
import pathlib
async def download_all(results, dest_dir="images"):
pathlib.Path(dest_dir).mkdir(exist_ok=True)
async with httpx.AsyncClient() as client:
tasks = []
for i, result in enumerate(results):
if result.get("url"):
fname = f"{dest_dir}/image_{i:05d}.jpg"
tasks.append(download_one(client, result["url"], fname))
await asyncio.gather(*tasks)
async def download_one(client, url, dest):
r = await client.get(url, timeout=30)
r.raise_for_status()
pathlib.Path(dest).write_bytes(r.content)
Run the download pass immediately after generation, before the temporary URLs expire.
FAQ
What concurrency is safe for batch image generation on GenRelay?
Start at 20 concurrent requests. Paid plans typically handle 50 concurrent requests cleanly. Monitor for 429 responses — if they appear, reduce concurrency or implement a token-bucket limiter.
Can I request multiple images per API call using n?
Yes. Setting "n": 4 generates four images in a single call, reducing per-request HTTP overhead. Billing is per image regardless of n. For large batches, n=4 with lower concurrency often performs similarly to n=1 at high concurrency.
Does batch image generation require a special API endpoint?
No. GenRelay uses the same /v1/images/generations endpoint for single and batch requests. Batching is a client-side concurrency pattern, not a separate API mode.
Are failed generation attempts billed?
No. If the API returns a 500 or model error, the request is not billed. 429 responses (rate limit exceeded) are also not billed.
How do I estimate the total cost before running a large batch?
Multiply number_of_images × per_image_cost. For a 10,000-image batch at Nano Banana 2 (1K): 10,000 × $0.020 = $200. See AI image API pricing comparison for a full breakdown.
Next steps
- Nano Banana Pro API overview — resolution options, quality parameters, use cases
- GPT-image-2 API guide — generation and editing capabilities
- AI image generation API pricing comparison — cost per model at different scales