Generate images with the Nano Banana API in Python
2 min readWritten by Unified Image API Team
This is the complete Python integration: submit → poll → download, with idempotency and error handling you can ship. The only dependency is requests.
Setup
Create an API key in the dashboard and export it:
export IMAGE_API_KEY=nb_live_...
export IMAGE_API_BASE=https://api.example.com # your base URL from the docsSubmitting a job
Generation is asynchronous: POST /v1/generations returns a job id in milliseconds, and the image is ready a few seconds later. Always send an Idempotency-Key — if your process crashes and retries, you will get the same job back instead of paying twice.
import os, time, uuid, requests
BASE = os.environ["IMAGE_API_BASE"]
HEADERS = {"Authorization": f"Bearer {os.environ['IMAGE_API_KEY']}"}
def submit(prompt: str, resolution: str = "1K", ratio: str = "1:1", model: str = "nanobanana") -> dict:
r = requests.post(
f"{BASE}/v1/generations",
headers={**HEADERS, "Idempotency-Key": str(uuid.uuid4())},
json={"model": model, "prompt": prompt, "n": 1, "resolution": resolution, "ratio": ratio},
timeout=10,
)
r.raise_for_status() # 202 Accepted
return r.json() # {"id": "...", "status": "queued", "cost": 2, ...}Polling correctly
Poll with backoff, not a tight loop — jobs typically finish in 1–20 seconds:
def wait(job_id: str, timeout_s: int = 120) -> dict:
deadline, delay = time.monotonic() + timeout_s, 0.5
while time.monotonic() < deadline:
job = requests.get(f"{BASE}/v1/generations/{job_id}", headers=HEADERS, timeout=10).json()
if job["status"] in ("succeeded", "failed", "cancelled"):
return job
time.sleep(delay)
delay = min(delay * 1.6, 5.0)
raise TimeoutError(job_id)Downloading results
Image URLs are presigned and expire after 15 minutes. If you need the file later, download it now; if a URL expired, just GET the job again for fresh ones.
def download(job: dict, path: str) -> None:
if job["status"] != "succeeded":
raise RuntimeError(f"{job['status']}: {job.get('error')}")
img = job["images"][0]
data = requests.get(img["url"], timeout=30).content
with open(path, "wb") as f:
f.write(data)
job = submit("a red fox in snow, studio lighting")
job = wait(job["id"])
download(job, "fox.png")Handling errors like a production service
Errors are application/problem+json; branch on code:
| code | What to do |
|---|---|
insufficient_credits (402) | Stop submitting; alert whoever owns the balance. |
rate_limited / too_many_inflight (429) | Sleep for Retry-After seconds, then retry the same request. |
model_unavailable (503) | Nothing was charged. Retry later or fall back to another model id. |
internal_error (500) | Retry with the same Idempotency-Key — you will not be double-charged. |
failed job status | Credits were refunded automatically; inspect error.code. |
That last line is the property worth designing around: a failed job costs nothing, so your retry logic never needs to reconcile money — just resubmit.
Next steps: swap model for nanobanana-pro for higher fidelity (comparison), or replace polling with webhooks.