Nano Banana Series
Model Introduction
Google's Nano Banana series provides image generation and editing and includes the following models:
gemini-nano-banana-2.1(Nano Banana 2.1)gemini-3.1-flash-image(Nano Banana 2)gemini-3-pro-image(Nano Banana Pro)gemini-2.5-flash-image(Nano Banana)
🆕 Nano Banana 2.1 (Newest)
- Model Name:
gemini-nano-banana-2.1 - Version: Generally available (GA)
- Features: Improved image detail, text rendering, and consistency across editing turns
- Resolutions:
1K,2K, and4K; defaults to1K;512is not supported - Reference Images: Up to 14 per request
- Use Cases: Text-to-image generation, image editing, and combining images
2.1 does not accept temperature, topP, topK, seed, or logprobs. Review these parameters when switching models. See the Google Cloud model specifications.
Endpoint Migration Notice
Note
The Nano Banana 2 and Pro preview models have been retired. Update to the stable model IDs below. See Google model deprecations for model status.
| Preview Endpoint | Model Endpoint |
|---|---|
gemini-3.1-flash-image-preview | gemini-3.1-flash-image |
gemini-3-pro-image-preview | gemini-3-pro-image |
Calling Method: Google Native Format
💡 Model Switching Instructions
In Google Native Format, the model name is specified in the URL path:
Nano Banana 2.1: /v1beta/models/gemini-nano-banana-2.1:generateContent
Nano Banana 2: /v1beta/models/gemini-3.1-flash-image:generateContent
Nano Banana Pro: /v1beta/models/gemini-3-pro-image:generateContent
Nano Banana: /v1beta/models/gemini-2.5-flash-image:generateContent
Send a POST request with an Exchange Token API Key in Authorization: Bearer $API_KEY and an application/json body. All models share the request structure; parameter support varies by model.
| Field | Requirement | Description |
|---|---|---|
contents | Required | A nonempty array of messages. For a single turn, include the current request; for multiple turns, include the conversation history and current request in order. |
contents[].role | Optional | Can be omitted for a single turn; typically user. In conversation history, use user and model to identify the sender of each message. |
contents[].parts | Required | A nonempty array of content parts. Put text instructions and reference images in separate parts. |
contents[].parts[].text | Optional | Instructions for generating or editing an image; recommended. |
contents[].parts[].inlineData.mimeType | Conditionally required | Required when providing a reference image through inlineData. Must match the actual image format, such as image/png or image/jpeg. |
contents[].parts[].inlineData.data | Conditionally required | Required when providing a reference image through inlineData. Use Base64 data without a data URL prefix. Up to 3 images for Nano Banana, and 14 for 2.1, 2, and Pro. |
contents[].parts[].thoughtSignature | Conditionally required | For multi-turn editing, if the previous model response includes this field, pass it back unchanged with its corresponding content part. Do not create a value for the initial request. See thought signatures. |
generationConfig.responseModalities | Optional | Explicitly setting this is recommended: ["TEXT", "IMAGE"] returns text and images; ["IMAGE"] requests images only. |
generationConfig.imageConfig.imageSize | Optional | Output resolution; use uppercase K, or 512 without a K suffix. See model support below; omit this parameter for Nano Banana. |
generationConfig.imageConfig.aspectRatio | Optional | Output aspect ratio, such as 1:1, 16:9, or 9:16. |
generationConfig.thinkingConfig.thinkingLevel | Optional | 2.1: MINIMAL, MEDIUM (default), HIGH; 2: MINIMAL (default), HIGH; Pro: HIGH; unsupported by Nano Banana. See thinking levels. |
Supported Aspect Ratios
| Type | Aspect Ratio Options | Supported Models |
|---|---|---|
| Landscape | 21:9 (Ultra Wide), 16:9 (Wide), 4:3, 3:2 | All models |
| Square | 1:1 | All models |
| Portrait | 9:16 (Vertical), 3:4, 2:3 | All models |
| Other | 5:4, 4:5 | All models |
| Additional Ratios | 1:4, 4:1, 1:8, 8:1, 9:21 | Nano Banana 2.1, Nano Banana 2, Nano Banana Pro |
Supported Resolutions
ℹ️ Resolution Support Guide
- Nano Banana 2.1 (
gemini-nano-banana-2.1): Supports1K,2K, and4K, defaulting to1K;512is not supported. - Nano Banana 2 (
gemini-3.1-flash-image): Supports 512, 1K, 2K, 4K. - Nano Banana Pro (
gemini-3-pro-image): Supports 1K, 2K, 4K. - Nano Banana (
gemini-2.5-flash-image): Approximately 1K, with dimensions varying by aspect ratio; does not supportimageSizeparameter.
Code Examples
Full Curl Example (Nano Banana 2.1 - 4K)
#!/bin/bash
set -euo pipefail
export API_KEY="sk-xxxx"
curl --fail-with-body -sS \
"https://api.exchangetoken.ai/v1beta/models/gemini-nano-banana-2.1:generateContent" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"contents": [{
"role": "user",
"parts": [{"text": "A futuristic city skyline at sunset, highly detailed"}]
}],
"generationConfig": {
"responseModalities": ["IMAGE"],
"imageConfig": {"aspectRatio": "16:9", "imageSize": "4K"}
}
}' -o response.json
jq -er '[.candidates[]?.content.parts[]?
| select(.thought != true and .inlineData.mimeType == "image/png")
| .inlineData.data][0] // error("No PNG image returned; check response.json")' \
response.json | base64 --decode > output_4k.png
echo "✅ Image saved: output_4k.png"Quick Start Curl Example (Nano Banana 2.1 1K)
Using the same API_KEY, request a 1K image with Nano Banana 2.1.
set -euo pipefail
# Use Nano Banana 2.1 (1K)
curl --fail-with-body -sS -X POST "https://api.exchangetoken.ai/v1beta/models/gemini-nano-banana-2.1:generateContent" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"contents": [{
"role": "user",
"parts": [{"text": "a beautiful sunset over mountains"}]
}],
"generationConfig": {
"responseModalities": ["IMAGE"],
"imageConfig": {"aspectRatio": "16:9", "imageSize": "1K"}
}
}' | jq -er '[.candidates[]?.content.parts[]? | select(.thought != true and .inlineData.mimeType == "image/png") | .inlineData.data][0] // error("No PNG image returned")' | base64 --decode > output.pngMore Code Scenarios
The following are advanced examples covering single image editing, multi-image synthesis, smart editing, and Base64 data processing. The Python examples use Nano Banana 2 and require requests and Pillow. Save the utility class in section 4 as Base64ImageHandler.py in the same directory.
1. Single Image Edit Example (Python)
Perform an edit operation on a single image.
import requests
import json
import base64
from Base64ImageHandler import Base64ImageHandler
def get_image_base64(url):
"""
[Critical Step]
This example downloads the image and passes it as Base64 inlineData.
"""
print(f"[LOG] Downloading image to adapt to native interface: {url}")
try:
headers = {'User-Agent': 'Mozilla/5.0'}
response = requests.get(url, headers=headers, timeout=30)
if response.status_code == 200:
# Get content-type, default is image/png
mime_type = response.headers.get('Content-Type', 'image/png').split(';', 1)[0]
# Convert to base64 string
b64_data = base64.b64encode(response.content).decode('utf-8')
print(f"[LOG] Image download successful, format: {mime_type}, data length: {len(b64_data)}")
return mime_type, b64_data
else:
print(f"[ERROR] Image download failed, status code: {response.status_code}")
return None, None
except Exception as e:
print(f"[ERROR] Image download exception: {e}")
return None, None
def creative_merge(image_urls, merge_instruction):
print("=" * 50)
print("Starting image merge request processing (Gemini native protocol)")
print("=" * 50)
# 1. Prepare request content parts
parts = []
# Add text instruction
parts.append({
"text": merge_instruction + "\n\nPlease return the generated image directly."
})
# 2. Process images: Download and convert to inline_data format
if image_urls:
print(f"[LOG] Number of image URLs: {len(image_urls)}")
for i, url in enumerate(image_urls, 1):
mime_type, image_b64 = get_image_base64(url)
if image_b64:
parts.append({
"inlineData": {
"mimeType": mime_type,
"data": image_b64
}
})
# 3. Construct Gemini native Payload (contents structure)
# Structure: contents -> parts -> [text, inlineData]
data = {
"contents": [
{
"role": "user",
"parts": parts
}
],
"generationConfig": {
"responseModalities": ["TEXT", "IMAGE"],
"maxOutputTokens": 4096
}
}
# 4. Set native interface URL
# Note: Using the :generateContent interface here
api_endpoint = "https://api.exchangetoken.ai/v1beta/models/gemini-3.1-flash-image:generateContent"
headers = {
"Authorization": f"Bearer sk-xxxxx",
"Content-Type": "application/json"
}
print(f"\n[LOG] Request URL: {api_endpoint}")
print(f"[LOG] Sending request...")
try:
response = requests.post(api_endpoint, headers=headers, json=data, timeout=180)
print(f"[LOG] HTTP Status Code: {response.status_code}")
# print(f"[LOG] Response Headers: {dict(response.headers)}")
if response.status_code != 200:
print(f"[ERROR] API request failed")
print(f"[ERROR] Response content: {response.text}")
return None, None
response_json = response.json()
print(f"\n[LOG] API call successful, starting to parse native response...")
# 5. Parse Gemini native response structure
# Images are returned in candidates[].content.parts[].inlineData
response_str = json.dumps(response_json, ensure_ascii=False)
# Attempt to print response text
try:
candidates = response_json.get('candidates', [])
if candidates:
content = candidates[0].get('content', {})
res_parts = content.get('parts', [])
if res_parts and 'text' in res_parts[0]:
print(f"[LOG] Model response text snippet: {res_parts[0]['text'][:200]}...")
except Exception as e:
print(f"[WARNING] Simple text parsing failed: {e}")
# Use the utility class to read inlineData from the native response
result = Base64ImageHandler.extract_from_response(response_str)
if result and result[0]:
print(f"[LOG] Successfully extracted image data, length: {len(result[0])}")
else:
print(f"[WARNING] Failed to extract base64 data, please check model response content.")
return result
except requests.exceptions.RequestException as e:
print(f"[ERROR] Request exception: {e}")
return None, None
except Exception as e:
print(f"[ERROR] Unknown error: {e}")
import traceback
traceback.print_exc()
return None, None
if __name__ == "__main__":
images = [
"https://your_image.png",
]
# Prompt: Image-to-Text-to-Image technique for multimodal models
prompt = (
"You are an image editing assistant. Please modify the person in the image to wear a stylish hat.\n"
"[Key]: Please output the modified image data directly, do not just output a description.\n"
"If you can generate the image directly, please return the image.\n"
)
result = creative_merge(images, prompt)
print("\n" + "=" * 50)
print("Save Image Result")
print("=" * 50)
if result and result[0]:
base64_data, image_format = result
print(f"[LOG] Preparing to save image, format: {image_format}")
saved_filename = Base64ImageHandler.save_to_file(base64_data, image_format=image_format)
print(f"[SUCCESS] Image saved as: {saved_filename}")
else:
print("[ERROR] Process ended, image not saved.")2. Multi-Image Synthesis - Creative Merge (Python)
Take multiple images as input and perform creative fusion through Prompt instructions.
import requests
import json
import base64
from Base64ImageHandler import Base64ImageHandler
def get_image_base64(url):
"""
[Critical Step]
This example downloads the image and passes it as Base64 inlineData.
"""
print(f"[LOG] Downloading image to adapt to native interface: {url}")
try:
headers = {'User-Agent': 'Mozilla/5.0'}
response = requests.get(url, headers=headers, timeout=30)
if response.status_code == 200:
# Get content-type, default is image/png
mime_type = response.headers.get('Content-Type', 'image/png').split(';', 1)[0]
# Convert to base64 string
b64_data = base64.b64encode(response.content).decode('utf-8')
print(f"[LOG] Image download successful, format: {mime_type}, data length: {len(b64_data)}")
return mime_type, b64_data
else:
print(f"[ERROR] Image download failed, status code: {response.status_code}")
return None, None
except Exception as e:
print(f"[ERROR] Image download exception: {e}")
return None, None
def creative_merge(image_urls, merge_instruction):
print("=" * 50)
print("Starting image merge request processing (Gemini native protocol)")
print("=" * 50)
# 1. Prepare request content parts
parts = []
# Add text instruction
parts.append({
"text": merge_instruction + "\n\nPlease return the generated image directly."
})
# 2. Process images: Download and convert to inline_data format
if image_urls:
print(f"[LOG] Number of image URLs: {len(image_urls)}")
for i, url in enumerate(image_urls, 1):
mime_type, image_b64 = get_image_base64(url)
if image_b64:
parts.append({
"inlineData": {
"mimeType": mime_type,
"data": image_b64
}
})
# 3. Construct Gemini native Payload (contents structure)
# Structure: contents -> parts -> [text, inlineData]
data = {
"contents": [
{
"role": "user",
"parts": parts
}
],
"generationConfig": {
"responseModalities": ["TEXT", "IMAGE"],
"maxOutputTokens": 4096
}
}
# 4. Set native interface URL
# Note: Using the :generateContent interface here
api_endpoint = "https://api.exchangetoken.ai/v1beta/models/gemini-3.1-flash-image:generateContent"
headers = {
"Authorization": f"Bearer sk-xxxxx",
"Content-Type": "application/json"
}
print(f"\n[LOG] Request URL: {api_endpoint}")
print(f"[LOG] Sending request...")
try:
response = requests.post(api_endpoint, headers=headers, json=data, timeout=180)
print(f"[LOG] HTTP Status Code: {response.status_code}")
# print(f"[LOG] Response Headers: {dict(response.headers)}")
if response.status_code != 200:
print(f"[ERROR] API request failed")
print(f"[ERROR] Response content: {response.text}")
return None, None
response_json = response.json()
print(f"\n[LOG] API call successful, starting to parse native response...")
# 5. Parse Gemini native response structure
# Images are returned in candidates[].content.parts[].inlineData
response_str = json.dumps(response_json, ensure_ascii=False)
# Attempt to print response text
try:
candidates = response_json.get('candidates', [])
if candidates:
content = candidates[0].get('content', {})
res_parts = content.get('parts', [])
if res_parts and 'text' in res_parts[0]:
print(f"[LOG] Model response text snippet: {res_parts[0]['text'][:200]}...")
except Exception as e:
print(f"[WARNING] Simple text parsing failed: {e}")
# Use the utility class to read inlineData from the native response
result = Base64ImageHandler.extract_from_response(response_str)
if result and result[0]:
print(f"[LOG] Successfully extracted image data, length: {len(result[0])}")
else:
print(f"[WARNING] Failed to extract base64 data, please check model response content.")
return result
except requests.exceptions.RequestException as e:
print(f"[ERROR] Request exception: {e}")
return None, None
except Exception as e:
print(f"[ERROR] Unknown error: {e}")
import traceback
traceback.print_exc()
return None, None
if __name__ == "__main__":
images = [
"https://image_one.png",
"https://image_two.png"
]
# Example Prompt: Image-to-Text-to-Image technique for multimodal models
prompt = (
"You are an image editing assistant. Please add a stylish hat to the person in the first image. "
"Also, place the gold necklace from the second image onto the person in the first image.\n"
"[Key]: Please output the modified image data directly, do not just output a description.\n"
"If you can generate the image directly, please return the image.\n"
)
result = creative_merge(images, prompt)
print("\n" + "=" * 50)
print("Save Image Result")
print("=" * 50)
if result and result[0]:
base64_data, image_format = result
print(f"[LOG] Preparing to save image, format: {image_format}")
saved_filename = Base64ImageHandler.save_to_file(base64_data, image_format=image_format)
print(f"[SUCCESS] Image saved as: {saved_filename}")
else:
print("[ERROR] Process ended, image not saved.")3. Smart Multi-Image Processing Strategy (Python)
Reuse the creative_merge function from the previous sections and adjust the prompt for single-image editing or multi-image synthesis.
def smart_multi_image_edit(images, instruction):
"""Smart multi-image editing, automatically handles different numbers of images"""
if len(images) == 1:
# Single image editing
prompt = f"Edit this image: {instruction}"
elif len(images) == 2:
# Two-image synthesis
prompt = f"Combine these two images creatively: {instruction}"
else:
# Multi-image processing
prompt = f"Process these {len(images)} images together: {instruction}"
return creative_merge(images, prompt)4. Base64 Data Processing Utilities (Python)
Utilities for processing Base64 image data returned by the API, supporting extraction, saving, and PIL format conversion.
import base64
import io
import json
from datetime import datetime
from PIL import Image
class Base64ImageHandler:
"""Base64 Image Handler Class"""
@staticmethod
def extract_from_response(response_content):
payload = json.loads(response_content) if isinstance(response_content, str) else response_content
for candidate in payload.get("candidates", []):
for part in candidate.get("content", {}).get("parts", []):
if part.get("thought"):
continue
image = part.get("inlineData", {})
mime_type = image.get("mimeType", "")
if mime_type in {"image/png", "image/jpeg", "image/webp", "image/heic", "image/heif"} and image.get("data"):
return image["data"], mime_type.split("/", 1)[1]
return None, None
@staticmethod
def save_to_file(base64_data, filename=None, image_format="png"):
"""Save base64 data as file"""
if not filename:
filename = f"image_{datetime.now().strftime('%Y%m%d_%H%M%S')}.{image_format}"
image_data = base64.b64decode(base64_data, validate=True)
with open(filename, 'wb') as f:
f.write(image_data)
return filename
@staticmethod
def to_pil_image(base64_data):
"""Convert to PIL Image object"""
image_data = base64.b64decode(base64_data, validate=True)
return Image.open(io.BytesIO(image_data))
@staticmethod
def from_pil_image(pil_image, format='PNG'):
"""Convert from PIL Image to base64"""
buffer = io.BytesIO()
pil_image.save(buffer, format=format)
img_str = base64.b64encode(buffer.getvalue()).decode()
return f"data:image/{format.lower()};base64,{img_str}"