# Nano Banana 2 (Gemini 3.1 Flash Image)

Nano Banana 2 is Google's Gemini 3.1 Flash Image model. Send Google's `generateContent` request body with a `model` field, keep the returned task ID, and check that task for the finished image. Reference images go in `contents` as `fileData` URLs.

## Model IDs

| Model ID                          | Channel  | Billing                                                          |
| --------------------------------- | -------- | ---------------------------------------------------------------- |
| `gemini-3.1-flash-image`          | Standard | One flat price per delivered image                               |
| `gemini-3.1-flash-image-official` | Official | Per-token rates for input, text/thinking output and image output |

Both IDs accept the same parameters. See the [model page](https://seedrouter.ai/models/nano-banana-2#pricing) for current prices.

## Quick example

```bash
curl https://api.seedrouter.ai/v1/images/generations \
  -H "Authorization: Bearer $SEEDROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gemini-3.1-flash-image",
    "contents": [{"parts": [{"text": "A ceramic teapot on a linen tablecloth, soft window light"}]}],
    "generationConfig": {
      "responseModalities": ["IMAGE"],
      "imageConfig": {"aspectRatio": "16:9", "imageSize": "2K"}
    }
  }'
```

```python
import os
import requests

response = requests.post(
    "https://api.seedrouter.ai/v1/images/generations",
    headers={"Authorization": f"Bearer {os.environ['SEEDROUTER_API_KEY']}"},
    json={
        "model": "gemini-3.1-flash-image",
        "contents": [{"parts": [{"text": "A ceramic teapot on a linen tablecloth, soft window light"}]}],
        "generationConfig": {
            "responseModalities": ["IMAGE"],
            "imageConfig": {"aspectRatio": "16:9", "imageSize": "2K"},
        },
    },
    timeout=60,
)
response.raise_for_status()
task_id = response.json()["id"]
```

```javascript
const response = await fetch('https://api.seedrouter.ai/v1/images/generations', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SEEDROUTER_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    model: 'gemini-3.1-flash-image',
    contents: [{ parts: [{ text: 'A ceramic teapot on a linen tablecloth, soft window light' }] }],
    generationConfig: {
      responseModalities: ['IMAGE'],
      imageConfig: { aspectRatio: '16:9', imageSize: '2K' },
    },
  }),
});
if (!response.ok) throw new Error(`Request failed: ${response.status}`);
const { id: taskId } = await response.json();
```

```go
package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "os"
    "strings"
    "time"
)

func main() {
    body := strings.NewReader(`{
      "model": "gemini-3.1-flash-image",
      "contents": [{"parts": [{"text": "A ceramic teapot on a linen tablecloth, soft window light"}]}],
      "generationConfig": {
        "responseModalities": ["IMAGE"],
        "imageConfig": {"aspectRatio": "16:9", "imageSize": "2K"}
      }
    }`)
    req, err := http.NewRequest("POST", "https://api.seedrouter.ai/v1/images/generations", body)
    if err != nil { panic(err) }
    req.Header.Set("Authorization", "Bearer " + os.Getenv("SEEDROUTER_API_KEY"))
    req.Header.Set("Content-Type", "application/json")
    client := &http.Client{Timeout: 60 * time.Second}
    res, err := client.Do(req)
    if err != nil { panic(err) }
    defer res.Body.Close()
    if res.StatusCode != http.StatusOK { panic(res.Status) }
    var task struct { ID string `json:"id"` }
    if err := json.NewDecoder(res.Body).Decode(&task); err != nil { panic(err) }
    fmt.Println(task.ID)
}
```

## Endpoint

```text
POST https://api.seedrouter.ai/v1/images/generations
```

| Header        | Value                 |
| ------------- | --------------------- |
| Authorization | `Bearer YOUR_API_KEY` |
| Content-Type  | `application/json`    |

The body is Google's `generateContent` request with one addition: `model`, because this endpoint carries no model in its path. The response contains a task ID, not the finished image. Keep API keys in server-side code. Calling `/v1beta/models/...:generateContent` directly is not supported; use this endpoint.

## Parameters

| Name                                              | Type       | Required | Default                       | Notes                                                                                                                                                                                                             |
| ------------------------------------------------- | ---------- | -------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `model`                                           | string     | Yes      | —                             | One of the two model IDs above.                                                                                                                                                                                   |
| `contents`                                        | Content\[] | Yes      | —                             | 1–32 turns. Each has `parts` and an optional `role` (`user` or `model`); the last turn is `user`.                                                                                                                 |
| `contents[].parts[].text`                         | string     | —        | —                             | A text part. At least one text part is required.                                                                                                                                                                  |
| `contents[].parts[].fileData`                     | object     | No       | —                             | `{"mimeType": "...", "fileUri": "https://..."}`; an image, video, or PDF reference. Up to 14 in total.                                                                                                            |
| `systemInstruction`                               | object     | No       | —                             | `{"parts": [{"text": "..."}]}`.                                                                                                                                                                                   |
| `safetySettings`                                  | object\[]  | No       | —                             | `{"category", "threshold"}` pairs; see below.                                                                                                                                                                     |
| `generationConfig.responseModalities`             | enum\[]    | No       | text and image                | `["IMAGE"]` for images only, or `["TEXT", "IMAGE"]`.                                                                                                                                                              |
| `generationConfig.imageConfig.aspectRatio`        | enum       | No       | Input image ratio, else `1:1` | `1:1`, `1:4`, `4:1`, `1:8`, `8:1`, `2:3`, `3:2`, `3:4`, `4:3`, `4:5`, `5:4`, `9:16`, `16:9`, `21:9`.                                                                                                              |
| `generationConfig.imageConfig.imageSize`          | enum       | No       | `1K`                          | `512`, `1K`, `2K`, `4K`. Uppercase `K`.                                                                                                                                                                           |
| `generationConfig.candidateCount`                 | integer    | No       | `1`                           | Only `1`. One request returns one image.                                                                                                                                                                          |
| `generationConfig.temperature`                    | number     | No       | Model default                 | 0–2.                                                                                                                                                                                                              |
| `generationConfig.topP`                           | number     | No       | Model default                 | 0–1.                                                                                                                                                                                                              |
| `generationConfig.topK`                           | integer    | No       | Model default                 | 1 or more.                                                                                                                                                                                                        |
| `generationConfig.seed`                           | integer    | No       | —                             | 32-bit integer.                                                                                                                                                                                                   |
| `generationConfig.maxOutputTokens`                | integer    | No       | Model default                 | 1–32,768.                                                                                                                                                                                                         |
| `generationConfig.stopSequences`                  | string\[]  | No       | —                             | Up to 5.                                                                                                                                                                                                          |
| `generationConfig.mediaResolution`                | enum       | No       | Model default                 | `MEDIA_RESOLUTION_LOW`, `MEDIA_RESOLUTION_MEDIUM`, `MEDIA_RESOLUTION_HIGH`. Sets how many tokens input media use.                                                                                                 |
| `generationConfig.thinkingConfig.includeThoughts` | boolean    | No       | `false`                       | Return the model's thought summaries as `output.thoughts`.                                                                                                                                                        |
| `generationConfig.responseFormat.image`           | object     | No       | —                             | `mimeType`: `IMAGE_JPEG`; `delivery`: `INLINE`; `aspectRatio` and `imageSize` as Google's enums, e.g. `ASPECT_RATIO_SIXTEEN_BY_NINE` and `IMAGE_SIZE_TWO_K`, covering the same ratios and sizes as `imageConfig`. |

Safety categories: `HARM_CATEGORY_HARASSMENT`, `HARM_CATEGORY_HATE_SPEECH`, `HARM_CATEGORY_SEXUALLY_EXPLICIT`, `HARM_CATEGORY_DANGEROUS_CONTENT`. Thresholds: `BLOCK_NONE`, `BLOCK_ONLY_HIGH`, `BLOCK_MEDIUM_AND_ABOVE`, `BLOCK_LOW_AND_ABOVE`, `OFF`.

Unknown fields are rejected. Not yet available: Google Search grounding (`tools`) and cached content; `thinkingLevel` is not documented for this model. `inlineData` is not accepted; pass media as `fileData` URLs. `responseFormat.image.delivery` accepts only `INLINE`: finished images are always returned as hosted URLs.

### Output size

| `imageSize` | 1:1 output | Image tokens |
| ----------- | ---------- | ------------ |
| `512`       | 512×512    | 747          |
| `1K`        | 1024×1024  | 1,120        |
| `2K`        | 2048×2048  | 1,680        |
| `4K`        | 4096×4096  | 2,520        |

Other aspect ratios keep the same token count; for example `16:9` at `1K` is 1376×768.

## Modes

There is no separate mode parameter or editing endpoint.

| Operation       | Parameters                                                                    |
| --------------- | ----------------------------------------------------------------------------- |
| Text to image   | a text part                                                                   |
| Edit or compose | text part + one or more `fileData` parts                                      |
| Multi-turn edit | earlier `user` and `model` turns, then a new `user` turn (see the note below) |

To continue a conversation, rebuild the `model` turn from the previous task's `output.parts`, in order: a text part becomes `{"text": ..., "thoughtSignature": ...}` and an image part becomes `{"fileData": {"mimeType": "image/<output_format>", "fileUri": <data[image].url>}, "thoughtSignature": ...}`. Keep each `thoughtSignature` exactly as returned: it is the URL of the signature we stored for you (a 4K image's signature is several megabytes), and we restore it before the request reaches the model. Only signatures from your own task results are accepted.

### Edit with a reference image

```bash
curl https://api.seedrouter.ai/v1/images/generations \
  -H "Authorization: Bearer $SEEDROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gemini-3.1-flash-image",
    "contents": [{
      "role": "user",
      "parts": [
        {"text": "Turn this photo into a watercolor painting. Keep the composition."},
        {"fileData": {"mimeType": "image/jpeg", "fileUri": "https://example.com/photo.jpg"}}
      ]
    }]
  }'
```

Replace the example URL with your own accessible image.

## Media inputs

This API accepts URL references only. Base64 `inlineData`, `data:` URLs, and multipart uploads are not accepted. The Playground uploads selected files to storage before submitting their URLs.

References must be public HTTP(S) URLs, each smaller than 50 MB and 100 MB in total: images (`image/png`, `image/jpeg`, `image/webp`, `image/heic`, `image/heif`), videos (`video/mp4`, `video/mpeg`, `video/mov`, `video/avi`, `video/x-flv`, `video/mpg`, `video/webm`, `video/wmv`, `video/3gpp`), or PDF documents (`application/pdf`). `mimeType` must match the file. URLs are fetched during processing; an inaccessible image fails the task, and a failed task is not charged.

## Pricing dimensions

Check the [model pricing section](https://seedrouter.ai/models/nano-banana-2#pricing) for current rates. `gemini-3.1-flash-image` charges one flat price per delivered image, whatever the size or prompt. `gemini-3.1-flash-image-official` charges by usage: input tokens (text and reference images), text and thinking output tokens, and image output tokens, each at its own rate. The image size is the main factor; see the table above.

View final charges in your account usage history. Failed tasks are not charged.

## Output schema

Submission returns a task reference:

```json
{
  "id": "task_...",
  "model": "gemini-3.1-flash-image",
  "status": "processing",
  "created_at": 1790310979
}
```

### Poll the task

```bash
curl https://api.seedrouter.ai/v1/tasks/YOUR_TASK_ID \
  -H "Authorization: Bearer $SEEDROUTER_API_KEY"
```

Poll every few seconds until `status` is `completed` or `failed`. A network timeout while polling does not mean generation failed: keep the task ID and resume checking it. Do not create another task to check progress.

### Complete polling example

Run this after the Python submission example above.

```python
import time

deadline = time.monotonic() + 600
while time.monotonic() < deadline:
    result = requests.get(
        f"https://api.seedrouter.ai/v1/tasks/{task_id}",
        headers={"Authorization": f"Bearer {os.environ['SEEDROUTER_API_KEY']}"},
        timeout=30,
    )
    result.raise_for_status()
    task = result.json()
    if task["status"] == "completed":
        for image in task["output"]["data"]:
            print(image["url"])
        break
    if task["status"] == "failed":
        raise RuntimeError(task["error"]["message"])
    time.sleep(3)
else:
    raise TimeoutError(f"Still waiting. Resume polling task {task_id}.")
```

### Completed task

```json
{
  "id": "task_...",
  "model": "gemini-3.1-flash-image",
  "status": "completed",
  "created_at": 1790310979,
  "finished_at": 1790311001,
  "output": {
    "created": 1790310999,
    "data": [{"url": "https://static.seedrouter.ai/media/tasks/task_example/0.jpg"}],
    "output_format": "jpeg",
    "usage": {
      "input_tokens": 27,
      "output_tokens": 1525,
      "total_tokens": 1552,
      "output_tokens_details": {"image_tokens": 1120, "text_tokens": 405, "reasoning_tokens": 0}
    }
  }
}
```

| Field                       | Meaning                                                                                                                                                                                               |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                        | Keep this ID for subsequent queries.                                                                                                                                                                  |
| `status`                    | `processing`, `completed`, or `failed`.                                                                                                                                                               |
| `created_at`, `finished_at` | Unix timestamps in seconds.                                                                                                                                                                           |
| `output.data[].url`         | The generated image URL.                                                                                                                                                                              |
| `output.text`               | Text the model returned alongside the image, when `responseModalities` includes `TEXT`. Thoughts are not included.                                                                                    |
| `output.thoughts`           | The model's thought summaries, when `includeThoughts` is `true`. Interim images the model draws while thinking are not delivered.                                                                     |
| `output.output_format`      | Actual image format.                                                                                                                                                                                  |
| `output.parts`              | The final response parts in order, for multi-turn editing: `{"text", "thoughtSignature"}` or `{"image": <index into data>, "thoughtSignature"}`. `thoughtSignature` is a URL; send it back unchanged. |
| `output.usage`              | Token usage. `output_tokens` counts text, thinking, and image output; `output_tokens_details.image_tokens` is the image part.                                                                         |
| `error`                     | Structured error on a failed task.                                                                                                                                                                    |

Streaming (`streamGenerateContent`) is not supported; results are delivered through the task.

## Errors

Requests rejected before a task is created return an HTTP error with an `error` object. A task that fails after acceptance returns HTTP 200 when queried, with `status: "failed"` and an `error` object. An image withheld by the model's safety filters fails with `content_policy_violation`; a response with no image fails with `no_output`.

See the [shared error catalog](https://seedrouter.ai/docs/api/errors) for codes, HTTP statuses, and retry guidance.

```json
{
  "id": "task_...",
  "status": "failed",
  "error": {
    "code": 60001,
    "message": "The request was rejected by the content policy. Please revise the prompt or input images."
  }
}
```

If submission itself times out, check your task history before submitting again: the first request may have been accepted.

## Tips

* Describe subject, setting, lighting, and style in full sentences.
* For an edit, say what should change and what must stay the same.
* Use `512` or `1K` for drafts, and `2K` or `4K` for final assets.
* Save returned images to your own storage when you need a durable copy.

## Related

* [Nano Banana 2 Playground and pricing](https://seedrouter.ai/models/nano-banana-2)
* [Download OpenAPI](https://seedrouter.ai/docs/gemini-3-1-flash-image.openapi.json)
* [Copyable Markdown](https://seedrouter.ai/docs/gemini-3-1-flash-image.md)
