PL Log inOpen Studio
Preview version. Model catalog, prices, calculator and docs are live. Generation goes live at launch.

AI Image Generation in Python: One Helper for GPT Image, Nano Banana and FLUX

Python AI image generation with one requests helper: text-to-image, edits with references, seeds, batches and parallel model tests on Kadraris API.

A Python script that makes AI images needs three things: an API key, a POST request with a model name and a prompt, and a download step for the file that comes back. On Kadraris API that is POST /v1/generate?wait=60 called with requests. The script stays the same whether you call GPT Image 2.5, Nano Banana Pro or FLUX.2 Pro; only the model string changes. You pay per image, and the price is visible before the job starts.

Below is the helper I keep in my own projects, followed by recipes for plain prompts, edits, seeds, batches and side-by-side tests.

Install and authenticate

You only need the requests package and a key from your account.

bash
pip install requests
export KADRARIS_API_KEY=sk-kd-...

The helper that does all the work

The function below sends the job with ?wait=60. The server keeps the connection open until the image is done (the cap is 120 seconds). If the job is still in progress when the wait ends, the loop keeps checking the task until it finishes.

python
import os
import time
import pathlib
import requests

API = "https://kadraris.com/v1"
KEY = os.environ["KADRARIS_API_KEY"]
HEADERS = {"Authorization": f"Bearer {KEY}"}

def generate(model, **input_):
    r = requests.post(
        f"{API}/generate?wait=60",
        headers=HEADERS,
        json={"model": model, "input": input_},
        timeout=130,
    )
    task = r.json()
    if r.status_code >= 400:
        msg = task["error"]["message"]
        raise RuntimeError(f"{r.status_code}: {msg}")
    # still busy after the wait? keep asking
    while task["status"] in ("queued", "running"):
        time.sleep(3)
        task = requests.get(
            f"{API}/tasks/{task['id']}",
            headers=HEADERS, timeout=30,
        ).json()
    if task["status"] == "failed":
        # failed jobs are free
        raise RuntimeError(f"failed: {task['error']}")
    return task

def save_outputs(task, folder="out"):
    pathlib.Path(folder).mkdir(exist_ok=True)
    paths = []
    for i, item in enumerate(task["output"]):
        clean = item["url"].split("?")[0]
        ext = pathlib.Path(clean).suffix or ".png"
        name = f"{task['id']}_{i}{ext}"
        path = pathlib.Path(folder) / name
        data = requests.get(item["url"], timeout=60).content
        path.write_bytes(data)
        paths.append(path)
    return paths

Call save_outputs straight after generate. Result links expire after a limited time, so treat them as a download slot, not storage. Your generation history stays on the account, but the file itself should live on your disk or bucket.

Your first image from a prompt

python
task = generate(
    "flux-2-pro",
    prompt=(
        "Product photo of a matte black espresso "
        "machine on a polished concrete counter, "
        "morning side light, shallow depth of field"
    ),
    aspect_ratio="4:3",
)
print(save_outputs(task), task["cost"])

To try a different model, swap the name. A poster with readable text, for example:

python
task = generate(
    "nano-banana-pro",
    prompt='Minimal concert poster, headline '
           '"NIGHT SHIFT", date "OCT 14", '
           "two-color risograph look, cream paper",
    aspect_ratio="3:4",
)

Fields every model shares, and fields that vary

Only two inputs behave the same across the image models:

FieldTypePurpose
prompttextDescribes the image
image_urlslist of public URLsReferences or the image to edit; the limit depends on the model

The rest is model-specific. A few examples:

  • aspect_ratio takes ratios such as 4:3 in FLUX.2 Pro and Nano Banana Pro. GPT Image 2.5 wants pixel sizes (1024x1024, 1536x1024, 1024x1536) or auto.
  • num_images, seed, negative_prompt, resolution and quality appear only in some schemas.

To see what a model accepts, read input_schema from GET /v1/models/{id}, for instance GET /v1/models/gpt-image-2.5. If you send a field or value the model does not know, you get a 422 response and nothing is charged.

Edits and reference images

For background swaps, style transfer or placing a product into a new scene, send one or more public image URLs in image_urls and write what should change.

python
task = generate(
    "gpt-image-2.5",
    prompt="Place this bottle on a wet rock by a "
           "mountain stream, overcast light, "
           "keep the label exactly as is",
    image_urls=[
        "https://example.com/assets/bottle.png",
    ],
    # pixel size here, this model has no ratios
    aspect_ratio="1536x1024",
)

With several references, number them in the prompt so the model knows which image supplies which element:

python
task = generate(
    "nano-banana-pro",
    prompt="The person from the first image wearing "
           "the jacket from the second image, walking "
           "down the street from the third image, "
           "golden hour",
    image_urls=[
        "https://example.com/refs/person.jpg",
        "https://example.com/refs/jacket.jpg",
        "https://example.com/refs/street.jpg",
    ],
    aspect_ratio="9:16",
)

What helped me get cleaner results:

  • The reference that matters most goes first in the list.
  • Product shots on a plain background and faces in good light work better than busy photos.
  • Name what must stay untouched, for example "keep the label exactly as is".

Picking between the three main models

These are the models I reach for first. The facts come from each producer's release notes.

GPT Image 2.5Nano Banana ProFLUX.2 Pro
ProducerOpenAIGoogle (Gemini 3 Pro Image)Black Forest Labs
ReleasedSept 8, 2026Nov 2025Nov 25, 2025
Outputup to 4Kup to 4Kup to 4 MP
Referencesup to 16up to 14several

And the strengths, in one line each:

  • GPT Image 2.5: careful edits and staying close to the reference photo. This was the focus of the 2.5 release. Price: $0.05 per image.
  • Nano Banana Pro: readable text in images, from short taglines to longer blocks and several languages. Good for posters, thumbnails, labels and infographics. Price: from $0.07 per image.
  • FLUX.2 [pro]: photoreal product and editorial shots with clean light and materials. Price: from $0.0688 per image.

If a brief does not point clearly at one of them, I run the prompt on all three with the script in the next section. For a longer comparison, see GPT Image 2.5 vs Nano Banana Pro vs FLUX.2.

Same prompt, three models, at once

Running models one by one means waiting three times. A thread pool sends the jobs together:

python
from concurrent.futures import ThreadPoolExecutor

PROMPT = ("Studio photo of a ceramic mug with "
          "'good morning' in hand-lettered script")
MODELS = ["gpt-image-2.5", "nano-banana-pro",
          "flux-2-pro"]
# pixel size for GPT Image, ratio for the others
ASPECT = {"gpt-image-2.5": "1024x1024",
          "nano-banana-pro": "1:1",
          "flux-2-pro": "1:1"}

def run(model):
    try:
        task = generate(model, prompt=PROMPT,
                        aspect_ratio=ASPECT[model])
        out = save_outputs(task, f"out/{model}")
        return model, out, task["cost"]
    except RuntimeError as e:
        return model, None, str(e)

with ThreadPoolExecutor(max_workers=3) as pool:
    for row in pool.map(run, MODELS):
        print(*row)

I start most new client briefs with this. One prompt, three files, and the winner is usually clear on first look. If a safety filter blocks the prompt on one model, that task ends as failed with cost 0 while the other two still return images.

Several variations in one job

Some models list num_images in their schema, Nano Banana 2 among them, and return up to four variations per task. Billing is per image, so four variations cost four images. GPT Image 2.5, Nano Banana Pro and FLUX.2 Pro give one image per task; for more, send more tasks.

python
task = generate(
    "nano-banana-2",
    prompt="Flat vector illustration of a fox "
           "reading a newspaper in a cafe, "
           "warm palette",
    aspect_ratio="1:1",
    num_images=4,
)
for p in save_outputs(task):
    print(p)

Repeatable results with a seed

A client approves a layout and then asks for "the same thing, with a blue roof". A fixed seed keeps the starting point stable between the two runs:

python
base = dict(
    prompt="Isometric tiny house on a floating "
           "island, soft clay render",
    aspect_ratio="1:1",
    seed=421,
)
a = generate("flux-2-max", **base)
blue = base["prompt"] + ", blue roof"
b = generate("flux-2-max", **{**base, "prompt": blue})

Keep expectations realistic: a seed makes output more repeatable, but a changed prompt still changes the picture. Not every model takes one. Among image models, FLUX.2 [max], FLUX.1 Kontext [pro] and Wan 2.7 Image accept seed. GPT Image 2.5, Nano Banana Pro and FLUX.2 Pro do not, and a request with seed returns 422. Look at the schema first.

Hundreds of images: go async

For large runs, drop ?wait and submit tasks without blocking. Collect results by polling GET /v1/tasks/{id} or by passing a webhook_url. The flow is the same as for video, described in async AI video with webhooks.

python
def submit(model, **input_):
    r = requests.post(
        f"{API}/generate", headers=HEADERS,
        json={"model": model, "input": input_},
        timeout=30,
    )
    r.raise_for_status()
    return r.json()["id"]

items = ["kettle", "bicycle", "tent", "lamp"]
prompts = [f"Minimal line icon of a {x}, "
           "black on white" for x in items]
ids = [submit("flux-2-pro", prompt=p,
              aspect_ratio="1:1") for p in prompts]

If you fire many requests in a short burst and see 429, wait a moment and retry.

Reading the cost

Every image model is billed per image. The 202 response includes estimated_cost, the finished task includes cost, and a failed task costs nothing. All rates are on /pricing, and the cost calculator helps when you plan a batch.

Turning a still into a clip

A good still often makes a good first frame for video. Pass the image URL as image_url to a video model such as Seedance 2.5 and describe the motion. Code for that is in the Seedance 2.5 API guide. If you prefer clicking to coding, the same models are in Kadraris Studio and the AI image generator, paid from the same balance.

Questions

Do I need an SDK to call the image models from Python?

No. Plain requests is enough. You send JSON with model and input, add the bearer key in the header, and read the image URL from output in the response.

Why does GPT Image 2.5 reject my aspect ratio?

It expects pixel sizes such as 1024x1024 or 1536x1024, or auto, rather than ratios like 4:3. Other models in this guide take ratios. The schema at GET /v1/models/{id} lists the allowed values.

Am I charged when a prompt is blocked or a job fails?

No. A failed task ends with cost 0 and the reserved amount goes back to your balance. A 422 for a bad field is returned before any charge.

How do I keep the generated files?

Download them right after the task succeeds, as the save_outputs helper does. The links in the response stop working after a limited time.