# Nano Banana Pro API: Python Tutorial, Edits and Price

> Call Nano Banana Pro from Python: the model id, size and input.image edit fields, the 4K option, Google's price per image, and the JSON each call returns.

- Canonical HTML: https://omniakey.com/blog/nano-banana-pro-api
- Markdown: https://omniakey.com/blog/nano-banana-pro-api.md
- Published: 2026-11-03 (updated 2026-11-03)
- Author: Kiki, COO at OmniaKey
- Language: en
- Category: API setup
- Reading time: 9 min
- All posts: https://omniakey.com/blog/llms.txt

---

Nano Banana Pro is Google's `gemini-3-pro-image`, and on OmniaKey you call it with one `POST /v1/media/tasks`. Our three 3:4 tests came back at 896x1200, 1792x2400 and 3584x4800, and every word in the poster was spelled right.

## Short answer

- **Model:** `gemini-3-pro-image` is Google's GA model id. The Vertex model card lists a release date of May 28, 2026.
- **Call:** send `POST https://api.omniakey.com/v1/media/tasks` with an `Idempotency-Key`, poll `GET /v1/media/tasks/{id}` until the status is succeeded, then download `assets[0].url`.
- **Parameters:** `parameters.aspect_ratio` and `parameters.image_size`; the size values are 1K, 2K and 4K, with an uppercase K.
- **Request limit:** 1 MiB for the whole body, including inline Base64 image data.
- **Google's price:** $0.134 per 1K/2K image and $0.24 per 4K image, from Google's Gemini API pricing page.
- **Our token counts:** the three tasks reported 1,133, 1,225 and 2,480 output tokens. At $120 per million image tokens that is $0.136, $0.147 and $0.298.
- **Edits:** use `operation: "edit"` and put the source in `input.image` as `{data, mime_type}`.

The [Nano Banana Pro model page](https://omniakey.com/gemini-3-pro-image) on OmniaKey also accepts `gemini-3-pro-image-preview`, the older snapshot id, at the same price.

## Model id and what you need

`gemini-3-pro-image` is the id to write in your code. Google's [Vertex model card](https://cloud.google.com/vertex-ai/generative-ai/docs/models/gemini/3-pro-image) lists it with launch stage GA and release date May 28, 2026. It supports 1K, 2K and 4K output.

The -preview id is the same model, not a separate one. OmniaKey's [image model table](https://docs.omniakey.com/en/media/image) lists `gemini-3-pro-image-preview` as "Same model, -preview snapshot id" at the same per-call rate.

You need two things:

1. An OmniaKey API key, exported so the script can read it: `export OMNIAKEY_API_KEY="your-key"`.
2. Python 3. The script below uses only the standard library: urllib, json, time, uuid, base64 and pathlib. No SDK to install.

## The Python script

Three steps: create the task, poll it, download the image. Save this as `nano_banana.py` and run it with `python3 nano_banana.py`.

```python
import base64
import json
import os
import time
import urllib.request
import uuid
from pathlib import Path

API = "https://api.omniakey.com/v1/media/tasks"
HEADERS = {
    "Authorization": f"Bearer {os.environ['OMNIAKEY_API_KEY']}",
    "Content-Type": "application/json",
}


def call(url, payload=None, headers=None, method=None):
    body = json.dumps(payload).encode() if payload is not None else None
    req = urllib.request.Request(url, data=body, headers=headers or HEADERS, method=method)
    with urllib.request.urlopen(req, timeout=60) as resp:
        return resp.status, json.loads(resp.read())


def create_task(prompt, aspect_ratio="3:4", image_size="2K", image_path=None):
    body = {
        "model": "gemini-3-pro-image",
        "type": "image",
        "operation": "generate" if image_path is None else "edit",
        "input": {"prompt": prompt},
        "parameters": {"aspect_ratio": aspect_ratio, "image_size": image_size},
    }
    if image_path:
        encoded = base64.b64encode(Path(image_path).read_bytes()).decode("utf-8")
        body["input"]["image"] = {"data": encoded, "mime_type": "image/png"}
    headers = {**HEADERS, "Idempotency-Key": str(uuid.uuid4())}
    status, task = call(API, body, headers, "POST")
    print("created", task["id"], "HTTP", status)
    return task


def wait(task_id, interval=3, timeout=300):
    deadline = time.time() + timeout
    while time.time() < deadline:
        _, task = call(f"{API}/{task_id}")
        print(task["status"], task["progress"]["percent"])
        if task["status"] in ("succeeded", "failed", "canceled"):
            return task
        time.sleep(interval)
    # A timeout is not a failure: keep the task id and poll it again.
    raise TimeoutError(f"task {task_id} is still running")


def download(task, out_path):
    asset = task["assets"][0]
    with urllib.request.urlopen(asset["url"], timeout=120) as resp:
        Path(out_path).write_bytes(resp.read())
    print("saved", out_path, asset["mime_type"], asset["byte_size"], "bytes")


if __name__ == "__main__":
    task = create_task(
        "A vintage travel poster for the Night Train from Zürich to Milano, "
        "departing 21:40 from platform 7. A steam train crosses a stone arch "
        "bridge below snowy mountains under a deep blue night sky, with a cream "
        "border. Spell every word exactly as written.",
        aspect_ratio="3:4",
        image_size="2K",
    )
    task = wait(task["id"])
    if task["status"] != "succeeded":
        raise SystemExit(f"{task['status']}: {task.get('error')}")
    download(task, "night-train.jpg")
```

`image_size` takes 1K, 2K or 4K. Google's image guide is explicit: "You must use an uppercase 'K' … Lowercase parameters (e.g., 1k) will be rejected." Send 2K, not 2k.

`Idempotency-Key` is required. A fresh UUID per request is the simple rule; reuse a key only when you retry the exact same body. OmniaKey returns the original task with HTTP 200 instead of charging twice, and answers 409 if the same key arrives with a different body.

The create call returns 202 with an id. OmniaKey's [media task docs](https://docs.omniakey.com/en/media/overview) describe the full lifecycle; the finished task carries the fields you read:

```json
{
  "id": "task_AbMRVqzSnVh0VwBj0S7NuMUxy5sZtfHP",
  "status": "succeeded",
  "progress": {"percent": 100, "stage": "completed"},
  "usage": {"total_tokens": 1133},
  "assets": [
    {
      "kind": "image",
      "mime_type": "…",
      "url": "https://…signed-url…"
    }
  ]
}
```

Read `assets[0].url` and download it right away: the signed link lasts about an hour, and the task record lasts 24 hours. The `mime_type` field tells you the format; Gemini models usually return JPEG.

The same script at 1:1 and 2K, with a Chinese sign prompt, returned this:

![A dark metal sign on a brick wall with three lines of Chinese text and a phone number](https://omniakey.com/_img/blog/nano-banana-pro-api/g2-sign.7e3e8e4d.1600.webp)

_2K, 1:1: 2048x2048 in 47.2 s. All three Chinese lines and 010-6789 4321 came back correct; rust covers part of the first 0._

## Editing an existing image

Set `operation` to `"edit"` and pass the source image in `input.image` as `{data, mime_type}`. The script above already does this when you give it an image\_path:

```python
task = create_task(
    "Hang this poster in a thin black frame on a warm white wall, with a brass "
    "picture light above it, a low wooden cabinet below and a trailing plant on "
    "the right. Keep the poster exactly as it is.",
    aspect_ratio="1:1",
    image_size="2K",
    image_path="night-train-2k.png",
)
```

The request body it sends looks like this:

```json
{
  "model": "gemini-3-pro-image",
  "type": "image",
  "operation": "edit",
  "input": {
    "prompt": "Hang this poster in a thin black frame on a warm white wall…",
    "image": {"data": "…base64…", "mime_type": "image/png"}
  },
  "parameters": {"aspect_ratio": "1:1", "image_size": "2K"}
}
```

The example source is a PNG, so the script labels it `image/png`; change that label for a JPEG. For several references, use `input.images` and give each entry the same `{data, mime_type}` shape. Google's guide puts the limit at 14 reference images; for `gemini-3-pro-image` the split is 6 object images, 5 character images and 3 style references.

Keep the body under 1 MiB. Base64 makes the file about a third larger, so resize or compress the source before you encode it. Our edit used the 2K poster and kept every word and the layout intact:

![The night-train poster in a black frame on a wall, with a picture light above it and a plant to the right](https://omniakey.com/_img/blog/nano-banana-pro-api/g3-edit.98011b5c.1600.webp)

_2K, 1:1 edit: 2048x2048 in 49.9 s. The poster text and design survived; the frame is a clean rectangle._

## 1K, 2K and 4K: what each buys

We ran the same 3:4 poster prompt at all three sizes. The pixel sizes matched Google's table for 3:4 exactly: 896x1200 at 1K, 1792x2400 at 2K and 3584x4800 at 4K. The poster text was correct at every size.

![The night-train poster at 1K: steam train on a stone bridge, snowy mountains and a deep blue sky](https://omniakey.com/_img/blog/nano-banana-pro-api/g1-1k.12bc0ed0.896.webp)

_1K, 3:4: 896x1200 in 28.4 s. All four poster lines are right; the locomotive's lamps and rods crowd together._

![The same night-train poster at 2K, with a village and two spires added on the right slope](https://omniakey.com/_img/blog/nano-banana-pro-api/g1-2k.38a932bb.1600.webp)

_2K, 3:4: 1792x2400 in 48.2 s. Same text, plus a village and two spires on the right slope._

![The same night-train poster at 4K, with three carriages and a tender drawn behind the locomotive](https://omniakey.com/_img/blog/nano-banana-pro-api/g1-4k.6c8048ad.1600.webp)

_4K, 3:4: 3584x4800 in 66.9 s. Three carriages and the tender are drawn; the brick seams vary in thickness._

What changes with size is detail, not spelling:

- **1K:** the train front is crowded: lamps, wheels and connecting rods sit close together.
- **2K:** the village and its two spires appear on the right slope, the chimney gets a pale thread of smoke, and the bridge shows seven arches.
- **4K:** the three carriages and the tender are separate, and the stonework shows brick seams of different thickness. The machinery under the locomotive is still simplified.

Speed went up with size: 28.4 s, 48.2 s and 66.9 s. We ran each size once, so read those seconds as a single run.

Google prices image output at $120 per million tokens, and its table gives 1,120 tokens for both 1K and 2K and 2,000 tokens for 4K. That works out to Google's own estimate:

```text
1,120 × $120 ÷ 1,000,000 = $0.134 per 1K image
1,120 × $120 ÷ 1,000,000 = $0.134 per 2K image
2,000 × $120 ÷ 1,000,000 = $0.24  per 4K image
```

Our tasks reported more tokens than Google's examples. Here is the same arithmetic on our counts:

```text
1,133 × $120 ÷ 1,000,000 = $0.136 per 1K image
1,225 × $120 ÷ 1,000,000 = $0.147 per 2K image
2,480 × $120 ÷ 1,000,000 = $0.298 per 4K image
```

|Size|Pixels, 3:4|Our time|Our tokens|Google estimate|
|-|-|-|-|-|
|1K|896x1200|28.4 s|1,133|$0.134|
|2K|1792x2400|48.2 s|1,225|$0.134|
|4K|3584x4800|66.9 s|2,480|$0.24|

OmniaKey bills an image call at a flat rate, not by token, so its charge does not move with those counts; the [model page](https://omniakey.com/gemini-3-pro-image) has the current per-call rate, and a failed task is refunded. Google's token-rate estimate for the same three images is $0.134, $0.134 and $0.24.

## Errors you will hit

The failures listed here mostly come down to a field name or a file size, not the model. These are the ones OmniaKey's media docs list for this endpoint:

|HTTP|Code or status|What it means|What to do|
|-|-|-|-|
|400|`invalid_request`|Malformed body, unsupported operation or bad parameter|Check the JSON and the field names below|
|503|`no_available_channel`|No channel currently serves this model|Switch models; retrying will not fix it|
|—|`failed`|The job finished without a result|Read `error.code` and `error.message`|

A failed task is not an HTTP error: the create call succeeded, so poll the task and read its `error` object. Failed and canceled jobs are refunded automatically.

A full-resolution source sent as Base64 can push the body over 1 MiB. Resize first, and check the encoded length, not just the file size.

Google's guide rejects a lowercase `1k`. Use 1K, 2K or 4K.

OmniaKey's image docs also reject `size` and a top-level `mime_type` on Gemini models, because those are not Gemini parameters. Use `parameters.aspect_ratio` and `parameters.image_size` instead, and read the output format from the response's `mime_type` field.

## Common questions

### What is the Nano Banana Pro API model id?

`gemini-3-pro-image`. Google's Vertex model card lists it as GA with a release date of May 28, 2026, and OmniaKey accepts the same id. The older `gemini-3-pro-image-preview` id still works on OmniaKey and points to the same model.

### How much does one Nano Banana Pro image cost?

Google estimates $0.134 for a 1K or 2K image and $0.24 for a 4K image at its $120 per million image-token rate. Our tasks reported 1,133, 1,225 and 2,480 tokens, which comes to $0.136, $0.147 and $0.298 at that rate. OmniaKey's model page lists its own per-call rate.

### Can Nano Banana Pro render Chinese text?

Yes. Our 2K sign came back with three correct Chinese lines — 同福客栈, 住宿 餐饮 茶水, and 010-6789 4321 — including all 11 digits and the hyphen.

### Should I request 1K, 2K or 4K?

For a poster like ours, 2K is the useful middle. All three sizes spelled our poster correctly, so 4K buys finer detail — separate carriages and brick seams — rather than better text. Use 1K for drafts where 896x1200 is enough.

### Why did my edit return 400 invalid\_request?

The usual causes are a body over 1 MiB, the source in the wrong field, or a Gemini model receiving `size` or a top-level `mime_type`. Put the image in `input.image` as `{data, mime_type}`, keep the encoded body under 1 MiB, and use `aspect_ratio` and `image_size`.

## Sources checked

- [Google Cloud: Gemini 3 Pro Image model card](https://cloud.google.com/vertex-ai/generative-ai/docs/models/gemini/3-pro-image) — checked November 3, 2026.
- [Google: Gemini API image generation guide](https://ai.google.dev/gemini-api/docs/image-generation) — checked November 3, 2026.
- [Google: Gemini API pricing](https://ai.google.dev/gemini-api/docs/pricing) — checked November 3, 2026.
- [Google: Nano Banana Pro announcement](https://blog.google/technology/ai/nano-banana-pro/) — checked November 3, 2026.

## Update log

- 2026-11-03: First version; five test calls run through OmniaKey that day.
