> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bettertoken.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Nano Banana API: generation, reference images, and editing

> Use Nano Banana Pro, 2, and 2.1 with BetterToken: authentication, resolutions, Python, reference images, saving results, multi-turn edits, and API limitations.

Use the same native Gemini endpoint for text-to-image, reference-image editing, and follow-up edits. Examples use `nano-banana-2.1`; replace the model in the URL or SDK call to use either of the other two.

* [Gemini text-to-image API](/en/api-reference/gemini-images-generations)
* [Gemini image editing API](/en/api-reference/gemini-images-edits)

<Warning>
  BetterToken API does not support search-assisted image generation, including Google Web Search and Image Search grounding. Do not add search tools.
</Warning>

## Connection details

| Field | Value |
| - | - |
| Base URL | `https://bettertoken.ai` |
| Request | `POST /v1beta/models/{model}:generateContent` |
| Authentication | `x-goog-api-key: YOUR_API_KEY`, or `Authorization: Bearer YOUR_API_KEY`. Choose one. |
| Body | `application/json` |
| Model IDs | `nano-banana-pro`, `nano-banana-2`, `nano-banana-2.1` |

<a href={"https://bettertoken.ai/register"}>Create a BetterToken API Key</a> · <a href={"https://bettertoken.ai/pricing"}>model plaza</a> · [Nano Banana model launch](/en/model-updates/nano-banana)

<Warning>
  These three Nano Banana models do not support `POST /v1/images/generations` or `POST /v1/images/edits`; those requests return `400 unsupported_endpoint`. Do not reuse GPT Image multipart, `image[]`, or `data[].b64_json` examples.
</Warning>

## Generate your first image

Set `BETTERTOKEN_API_KEY` in your terminal, then run the request. It waits for generation and saves the full JSON response to `response.json`.

<Tabs>
  <Tab title="macOS / Linux">
    ```bash theme={null}
    export BETTERTOKEN_API_KEY="YOUR_API_KEY"

    curl --fail-with-body --max-time 650 \
      "https://bettertoken.ai/v1beta/models/nano-banana-2.1:generateContent" \
      -H "x-goog-api-key: $BETTERTOKEN_API_KEY" \
      -H "Content-Type: application/json" \
      -H "X-BetterToken-Max-Images: 1" \
      -d '{
        "contents": [{
          "role": "user",
          "parts": [{"text": "A studio product photo of a blue ceramic mug on a white table."}]
        }],
        "generationConfig": {
          "responseModalities": ["IMAGE"],
          "candidateCount": 1,
          "imageConfig": {"imageSize": "1K", "aspectRatio": "1:1"}
        }
      }' -o response.json
    ```
  </Tab>

  <Tab title="Windows PowerShell">
    ```powershell theme={null}
    $env:BETTERTOKEN_API_KEY = "YOUR_API_KEY"
    $headers = @{
        "x-goog-api-key" = $env:BETTERTOKEN_API_KEY
        "X-BetterToken-Max-Images" = "1"
    }
    $body = @{
        contents = @(@{
            role = "user"
            parts = @(@{ text = "A studio product photo of a blue ceramic mug on a white table." })
        })
        generationConfig = @{
            responseModalities = @("IMAGE")
            candidateCount = 1
            imageConfig = @{ imageSize = "1K"; aspectRatio = "1:1" }
        }
    } | ConvertTo-Json -Depth 10
    Invoke-WebRequest `
        -Uri "https://bettertoken.ai/v1beta/models/nano-banana-2.1:generateContent" `
        -Method Post `
        -Headers $headers `
        -ContentType "application/json" `
        -Body ([System.Text.Encoding]::UTF8.GetBytes($body)) `
        -TimeoutSec 650 `
        -OutFile "response.json"
    ```
  </Tab>
</Tabs>

Change `imageSize` to `2K` or `4K` for higher resolution. Start with `1K`, `1:1`, and one image. Do not add automatic generation retries with `--retry`.

## Common parameters

For product photography, describe the subject, material, lighting, and background. For posters, specify the exact text and layout. For edits, state what should change and what should stay unchanged.

Use these fields in native HTTP requests; Python SDK names use snake\_case. Place image settings inside `generationConfig.imageConfig`.

| Field | Value |
| - | - |
| `contents[].parts[].text` | Describe the subject, background, style, and changes. |
| `contents[].parts[].inlineData` | Reference image `mimeType` and base64 of the original file, not an image URL. |
| `generationConfig.responseModalities` | Explicitly set `["IMAGE"]`. |
| `generationConfig.imageConfig.imageSize` | Case-sensitive `1K`, `2K`, or `4K`; defaults to `1K`. |
| `generationConfig.imageConfig.aspectRatio` | Choose a ratio for your model from the table below; examples use `1:1`. |
| `generationConfig.candidateCount` | Omit or set integer `1`; do not send root-level `n` in native requests. |
| `X-BetterToken-Max-Images` | Optional header: integer `1` through `4`, default `1`. Maximum number of output images you authorize for delivery and billing. |

<span id="aspect-ratios" />

### Google-supported aspect ratios

Nano Banana 2 and 2.1 support 14 ratios; Pro supports 10. Start with `1:1` for your first request.

| Model | Resolution | Aspect ratios |
| - | - | - |
| `nano-banana-pro` | `1K`, `2K`, `4K` | `1:1`, `2:3`, `3:2`, `3:4`, `4:3`, `4:5`, `5:4`, `9:16`, `16:9`, `21:9` |
| `nano-banana-2` | `1K`, `2K`, `4K` | `1:1`, `1:4`, `1:8`, `2:3`, `3:2`, `3:4`, `4:1`, `4:3`, `4:5`, `5:4`, `8:1`, `9:16`, `16:9`, `21:9` |
| `nano-banana-2.1` | `1K`, `2K`, `4K` | `1:1`, `1:4`, `1:8`, `2:3`, `3:2`, `3:4`, `4:1`, `4:3`, `4:5`, `5:4`, `8:1`, `9:16`, `16:9`, `21:9` |

[Google aspect-ratio and image-size reference](https://ai.google.dev/gemini-api/docs/image-generation#aspect-ratios-and-image-size)

The output limit does not guarantee an image count. The model may return one image even if asked for more. For a fixed number of images, send separate requests.

## Read and save the image

Images are in `candidates[].content.parts[].inlineData`. The example below shows the response shape; `BASE64_IMAGE_DATA` is a placeholder. Read all final image parts and skip intermediate parts marked `thought: true`.

```json theme={null}
{
  "candidates": [{
    "content": {
      "role": "model",
      "parts": [{
        "inlineData": {
          "mimeType": "image/jpeg",
          "data": "BASE64_IMAGE_DATA"
        }
      }]
    },
    "finishReason": "STOP"
  }]
}
```

Run this Python code to save the curl result. Select the extension from `mimeType`: an image may be JPEG, so do not always save it as `.png`.

```python theme={null}
import base64
import json
from pathlib import Path

suffixes = {"image/png": ".png", "image/jpeg": ".jpg"}
result = json.loads(Path("response.json").read_text())
saved = 0
for candidate in result.get("candidates", []):
    for part in candidate.get("content", {}).get("parts", []):
        if part.get("thought"):
            continue
        blob = part.get("inlineData")
        if not blob:
            continue
        suffix = suffixes.get(blob["mimeType"])
        if suffix is None:
            raise ValueError("Unexpected image MIME type: " + blob["mimeType"])
        saved += 1
        Path(f"generated-{saved}{suffix}").write_bytes(
            base64.b64decode(blob["data"], validate=True)
        )
if saved == 0:
    raise RuntimeError("No final image returned; inspect response.json.")
```

## Complete Python SDK example

Use Python 3.10 or later. This example targets `google-genai==2.30.1`. Install it in a project virtual environment; older SDK releases may omit `image_size` from the request.

```bash theme={null}
python -m pip install "google-genai==2.30.1"
```

The SDK Base URL is the domain root, without `/v1` or `/v1beta`. `timeout` is in milliseconds; `attempts=1` disables automatic retries. The `save_images` helper saves every final image.

```python theme={null}
import os
from pathlib import Path
from google import genai
from google.genai import types

MODEL = "nano-banana-2.1"
client = genai.Client(
    vertexai=False,
    api_key=os.environ["BETTERTOKEN_API_KEY"],
    http_options=types.HttpOptions(
        base_url="https://bettertoken.ai",
        api_version="v1beta",
        timeout=650_000,
        retry_options=types.HttpRetryOptions(attempts=1),
        headers={"X-BetterToken-Max-Images": "1"},
    ),
)
config = types.GenerateContentConfig(
    response_modalities=["IMAGE"],
    candidate_count=1,
    image_config=types.ImageConfig(image_size="1K", aspect_ratio="1:1"),
)

def save_images(response, prefix):
    if len(response.candidates or []) != 1 or not response.candidates[0].content:
        raise RuntimeError("No single image candidate returned.")
    saved = 0
    suffixes = {"image/png": ".png", "image/jpeg": ".jpg"}
    for part in response.candidates[0].content.parts or []:
        if part.thought or not part.inline_data:
            continue
        blob = part.inline_data
        suffix = suffixes.get(blob.mime_type)
        if suffix is None:
            raise ValueError("Unexpected image MIME type: " + str(blob.mime_type))
        saved += 1
        Path(f"{prefix}-{saved}{suffix}").write_bytes(blob.data)
    if saved == 0:
        raise RuntimeError("No final image returned; inspect the response.")

first_turn = types.Content(
    role="user",
    parts=[types.Part(text="A studio product photo of a blue ceramic mug on a white table.")],
)
response = client.models.generate_content(
    model=MODEL, contents=[first_turn], config=config,
)
save_images(response, "generated")
```

## Send a reference image

Run the following after the Python example to send a new editing request. Replace `reference.jpg` with your local file. For PNG, use `mime_type="image/png"`. Add further reference images as parts in the same user message.

```python theme={null}
first_turn = types.Content(
    role="user",
    parts=[
        types.Part(text="Change the mug to red. Preserve its shape and the background."),
        types.Part.from_bytes(
            data=Path("reference.jpg").read_bytes(),
            mime_type="image/jpeg",
        ),
    ],
)
response = client.models.generate_content(
    model=MODEL, contents=[first_turn], config=config,
)
save_images(response, "reference-edit")
```

In native JSON, use `inlineData: {"mimeType": "image/jpeg", "data": "BASE64_IMAGE_DATA"}`. Encode the original file bytes without a `data:image/...;base64,` prefix. This integration uses neither multipart nor Files uploads or `fileData`.

## Continue editing

Run this after a successful generation or reference edit. Preserve the entire model response `content`, including part order and `thoughtSignature`. Do not rebuild it from the image alone or invent or alter signatures.

```python theme={null}
history = [first_turn, response.candidates[0].content]
edit_turn = types.Content(
    role="user",
    parts=[types.Part(text="Add a small gold handle. Keep everything else unchanged.")],
)
edited = client.models.generate_content(
    model=MODEL, contents=[*history, edit_turn], config=config,
)
save_images(edited, "follow-up")
history.extend([edit_turn, edited.candidates[0].content])
```

Each turn is a new generation request. For further edits, keep the full `history`, append the new user instruction, then append the returned model `content`. Do not use the Interactions field `previous_interaction_id` with this endpoint.

## API support

* Use non-streaming `generateContent`. Files / `fileData`, Batch, Interactions, and streaming are not supported by this integration.
* `responseMimeType`, `responseSchema`, and custom image compression formats are not supported. Use only the two `imageConfig` fields listed above.
* Omit `cachedContent`, function calling, code execution, file search, Maps, URL context, and audio input or generation settings.

## Using an existing OpenAI Chat client

You can call `POST /v1/chat/completions` with Base URL `https://bettertoken.ai/v1`. Use the Gemini native endpoint above for new integrations, reference images, and multi-turn editing. In Chat requests, omit `n` or set it to integer `1`.

With raw HTTP, put `google.image_config` at the JSON root. In the Python OpenAI SDK, pass it through `extra_body`, which the SDK merges into the root. Do not use `generationConfig` here.

```bash theme={null}
curl --fail-with-body --max-time 650 \
  "https://bettertoken.ai/v1/chat/completions" \
  -H "Authorization: Bearer $BETTERTOKEN_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-BetterToken-Max-Images: 1" \
  -d '{
    "model": "nano-banana-2.1",
    "messages": [{"role": "user", "content": "A blue ceramic mug on a white table."}],
    "n": 1,
    "stream": false,
    "google": {"image_config": {"image_size": "1K", "aspect_ratio": "1:1"}}
  }'
```

For the Python OpenAI SDK example, install `openai==1.66.2` in the same virtual environment, then run:

```python theme={null}
import base64
import os
import re
from pathlib import Path
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["BETTERTOKEN_API_KEY"],
    base_url="https://bettertoken.ai/v1",
    max_retries=0,
    timeout=650,
)
response = client.chat.completions.create(
    model="nano-banana-2.1",
    messages=[{"role": "user", "content": "A blue ceramic mug on a white table."}],
    n=1,
    stream=False,
    extra_headers={"X-BetterToken-Max-Images": "1"},
    extra_body={
        "google": {"image_config": {"image_size": "1K", "aspect_ratio": "1:1"}}
    },
)
content = response.choices[0].message.content or ""
images = re.findall(r"data:(image/(?:png|jpeg));base64,([A-Za-z0-9+/=]+)", content)
if not images:
    raise RuntimeError("No inline image in Chat response.")
for index, (mime, data) in enumerate(images, 1):
    suffix = ".png" if mime == "image/png" else ".jpg"
    Path(f"chat-{index}{suffix}").write_bytes(base64.b64decode(data, validate=True))
```

Chat returns images as Markdown Data URLs in `choices[0].message.content`, not the Images API `data[]` array. Do not reconstruct signed edit history from this text; use the native editing example.

## Common errors

| Problem | Action |
| - | - |
| `401` / invalid key | Use a BetterToken API Key, not a Google API key. Check the environment variable and header. |
| `404` / incorrect URL | Use the domain root as the SDK Base URL, `api_version="v1beta"`, and one of the three public Model IDs. |
| `400 unsupported_endpoint` | Replace `/v1/images/*` with the `:generateContent` endpoint above. |
| `400` / invalid parameters | Check nesting, uppercase `1K/2K/4K`, and the selected model’s aspect ratios. Omit `candidateCount` or set integer `1`; do not add search tools. |
| Successful response without a final image | Inspect errors, safety feedback, text, and `finishReason` in the full response. Do not save thought images as final results. |
| Saved file will not open | Use the MIME extension and decode base64. Do not save the entire JSON or Markdown as an image. |
| Timeout or disconnected request | Check usage records and keep any request ID from the response. Confirm the outcome with support before retrying to avoid duplicate generation and charges. |

Images are delivered in the current response. This workflow has no background image retrieval or task polling endpoint. Save successful results promptly, allow enough time in both SDK and proxy, and disable automatic retries.

## Official sources

* [Nano Banana Pro model](https://ai.google.dev/gemini-api/docs/models/gemini-3-pro-image)
* [Nano Banana 2 model](https://ai.google.dev/gemini-api/docs/models/gemini-3.1-flash-image)
* [Nano Banana 2.1 model](https://ai.google.dev/gemini-api/docs/models/gemini-nano-banana-2.1)
* [Google image generation guide](https://ai.google.dev/gemini-api/docs/generate-content/image-generation)
* [Google release notes](https://ai.google.dev/gemini-api/docs/changelog)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.