Genace

Image generation API Python: collect every generated image safely

Python is a useful fit when generated images feed a catalog, notebook, or asset processing script. The important detail is that the image route also creates an asynchronous job. It does not return an OpenAI-style immediate data array. This tutorial collects an output manifest instead of assuming the first response already contains image bytes.

1. Prepare an isolated environment

Install requests in your project environment and set GENACE_API_KEY outside the source file. Start with one image while you are checking the workflow. The Nano Banana adapter accepts integer n values from one through four; that is the batch size for a single job, not an instruction to launch multiple requests.

python -m venv .venv
python -m pip install requests

Activate the virtual environment for your shell before installing when you use that environment. Save the following code as images.py and run it only when you want to submit a real generation.

import json
import os
import time
from pathlib import Path
from urllib.parse import urlparse
import requests

base = "https://genace.ai/api/v1"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['GENACE_API_KEY']}"

response = session.post(f"{base}/images/generations", json={
    "model": "nano-banana",
    "prompt": "Studio photo of a blue ceramic cup on a pale wood table",
    "aspect_ratio": "1:1",
    "n": 2,
}, timeout=45)
response.raise_for_status()
accepted = response.json()
job_id = accepted["job_id"]
Path("image-job.json").write_text(json.dumps(accepted), encoding="utf-8")

deadline = time.monotonic() + 600
while time.monotonic() < deadline:
    response = session.get(f"{base}/jobs/{job_id}",
                           params={"wait": 30}, timeout=45)
    response.raise_for_status()
    job = response.json()
    if job["state"] in {"succeeded", "failed", "canceled"}:
        break
else:
    raise TimeoutError(f"Resume observation of {job_id}")

if job["state"] != "succeeded":
    raise RuntimeError({"state": job["state"], "error": job.get("error")})
images = [item for item in (job.get("outputs") or [])
          if item.get("type") == "image"]
if not images:
    raise RuntimeError("No image outputs")
for image in images:
    parsed = urlparse(image["url"])
    if parsed.scheme != "https" or not parsed.netloc:
        raise ValueError("Unexpected output URL")
Path("image-outputs.json").write_text(json.dumps({
    "job_id": job_id, "images": images,
}, indent=2), encoding="utf-8")

2. Keep transport and generation errors separate

raise_for_status() detects an unsuccessful HTTP response. It does not inspect the state inside a successful job response. The explicit terminal-state branch catches that second class of failure. Retain the error object rather than reporting only that indexing an empty list failed.

The script preserves the accepted response immediately. If observation times out, use the saved identifier for subsequent GET requests. Repeating the whole script starts another batch, which is a separate operation with its own credit reservation. If you add retry middleware, configure it around status requests first and treat uncertain POST outcomes as an investigation branch.

3. Decide what the manifest means

This example records URLs and metadata, not downloaded files. It deliberately avoids inventing a URL retention guarantee. If your application requires durable assets, create a separate authenticated worker that downloads selected outputs under your storage policy. Record which downloads succeeded so that a storage retry does not create another image batch.

Do not silently assume every successful job contains exactly the requested number of outputs. Validate the returned list against your application requirement, and record discrepancies explicitly. The adapter treats an entirely empty upstream result as a job failure, but your application may still need stricter completeness checks.

Verification checklist

  • Verify a pending fixture with outputs: null remains pending.
  • Verify failed status responses produce a useful exception.
  • Verify the manifest contains the accepted job ID and all image outputs.
  • Verify a resumed status check does not repeat the submission.

See image batch behavior and Nano Banana parameters when tuning batch size or aspect ratio.

Continue with code and integration

Models covered on this page

Where these facts come from

  • Editorial keyword hypothesis; search volume not measured
  • codebase: src/app/api/v1/images/generations/route.ts
  • codebase: src/ai/providers/nano-banana.ts
  • codebase: src/ai/jobs/http.ts