Gemini Native
Call π Nano Banana through Google Gen AI SDK or HTTP, with multimodal inputs and image output.
Switch protocols here
pip install google-genaiimport jsonfrom google import genaifrom google.genai import typesapi_key = "YOUR_API_KEY"api_base = "https://api.1route.dev"payload = { "contents": [ { "role": "user", "parts": [ { "text": "Editorial portrait of a woman in profile beside a wind-bent tree, late afternoon light, deep cobalt coat, pale concrete wall, subtle film grain, restrained color palette, clean composition, vertical 4:5." } ] } ], "generationConfig": { "responseModalities": [ "TEXT", "IMAGE" ], "imageConfig": { "imageSize": "1K", "aspectRatio": "1:1" }, "responseFormat": { "image": { "delivery": "INLINE" } } }}client = genai.Client( api_key=api_key, http_options=types.HttpOptions(base_url=api_base, api_version="v1beta", timeout=1500000),)response = client.models.generate_content( model="gemini-3.1-flash-image", contents=payload["contents"], config=types.GenerateContentConfig( http_options=types.HttpOptions(extra_body=payload), ),)result = response.model_dump(mode="json", by_alias=True, exclude_none=True)print(json.dumps(result, ensure_ascii=False, indent=2))Generate an image
https://api.1route.dev/v1beta/models/{model}:generateContenthttps://api.1route.devFrequent timeouts? Use https://image-api.1route.devThe path selects the model: gemini-3.1-flash-image is π Nano Banana 2 and gemini-3-pro-image is π Nano Banana Pro. Send JSON with x-goog-api-key: YOUR_API_KEY or Authorization: Bearer YOUR_API_KEY.
{
"contents": [{
"role": "user",
"parts": [{ "text": "A spring menu poster for a coffee shop, title SPRING MENU" }]
}],
"generationConfig": {
"responseModalities": ["TEXT", "IMAGE"],
"imageConfig": { "imageSize": "2K", "aspectRatio": "3:4" }
}
}Request structure
Request
ββ contents[] Ordered conversation turns
β ββ role user or model
β ββ parts[] Contents of this turn
β ββ text Text prompt
β ββ inlineData Local image as Base64
β ββ fileData Image URL
ββ generationConfig Generation options
ββ responseModalities Text, images, or both
ββ imageConfig Dimensions and aspect ratio
ββ imageSize 1K, 2K, 4K
ββ aspectRatio e.g. 1:1, 3:4, 16:9contents holds turns; parts holds individual pieces within a turn. Each part carries one content type. Text and multiple references can share a turn. Include IMAGE for image generation and TEXT for accompanying text. The whole response is not a single image.
Use uppercase size tiers. Without explicit dimensions, the model chooses them. π Nano Banana 2 / Pro commonly use 1K, 2K, or 4K with ratios 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, or 21:9.
Official Python SDK
Install google-genai. Use the service origin as base_url, without /v1, and set api_version separately. This example saves images and prints accompanying text.
from google import genai
from google.genai import types
from pathlib import Path
client = genai.Client(
api_key="YOUR_API_KEY",
http_options=types.HttpOptions(
base_url="https://api.1route.dev",
api_version="v1beta",
timeout=1500000,
),
)
response = client.models.generate_content(
model="gemini-3.1-flash-image",
contents="A spring menu poster for a coffee shop, title SPRING MENU",
config=types.GenerateContentConfig(
response_modalities=["TEXT", "IMAGE"],
image_config=types.ImageConfig(image_size="2K", aspect_ratio="3:4"),
),
)
for index, part in enumerate(response.candidates[0].content.parts):
if part.text:
print(part.text)
elif part.inline_data:
Path(f"output-{index}.png").write_bytes(part.inline_data.data)SDK inline_data.data is already bytes; do not decode it again. Raw HTTP inlineData.data is a Base64 string. The JavaScript SDK is @google/genai; the playground also provides httpx, requests, and fetch.
Reference images
Editing also uses generateContent. Put the prompt and images together in contents[].parts.
Local files
{
"contents": [{
"role": "user",
"parts": [
{ "text": "Preserve the person's facial features. Change the coat to dark green." },
{ "inlineData": { "mimeType": "image/png", "data": "<Base64 without a Data URL prefix>" } }
]
}],
"generationConfig": {
"responseModalities": ["TEXT", "IMAGE"],
"imageConfig": { "imageSize": "1K", "aspectRatio": "3:4" }
}
}Match mimeType to the actual file, for example image/png, image/jpeg, or image/webp. The SDK can create a part directly from bytes:
reference = types.Part.from_bytes(
data=Path("portrait.png").read_bytes(),
mime_type="image/png",
)
response = client.models.generate_content(
model="gemini-3-pro-image",
contents=["Preserve the face and change the coat to dark green", reference],
config=types.GenerateContentConfig(response_modalities=["TEXT", "IMAGE"]),
)Image URLs
Use fileData with a directly downloadable image URL and its MIME type:
{
"fileData": {
"mimeType": "image/jpeg",
"fileUri": "https://your-image-host.example/portrait.jpg"
}
}You can mix multiple fileData and inlineData parts; order is preserved. The server downloads URLs before submitting the images. Individual media is limited to 80 MiB and the total request body to 512 MiB; model input limits also apply.
Image delivery
Images default to inlineData. To receive an image URL, use this service's delivery extension:
{
"generationConfig": {
"responseModalities": ["TEXT", "IMAGE"],
"imageConfig": { "imageSize": "2K", "aspectRatio": "16:9" },
"responseFormat": { "image": { "delivery": "URI" } }
}
}INLINE returns Base64; URI returns fileData.fileUri. URLs are retained for 72 hours, then return 410 result_expired. Media endpoints support GET and HEAD. Download the image for long-term use.
Dimension field versions
Both imageConfig shorthand and responseFormat.image enums are accepted:
{
"generationConfig": {
"responseModalities": ["TEXT", "IMAGE"],
"responseFormat": {
"image": {
"imageSize": "IMAGE_SIZE_TWO_K",
"aspectRatio": "ASPECT_RATIO_SIXTEEN_BY_NINE",
"delivery": "URI"
}
}
}
}1K, 2K, and 4K map to IMAGE_SIZE_ONE_K, IMAGE_SIZE_TWO_K, and IMAGE_SIZE_FOUR_K. When both structures set the same field, responseFormat.image wins. OpenAI size and response_format do not apply here.
Pass service extensions not covered by the SDK through http_options.extra_body:
response = client.models.generate_content(
model="gemini-3.1-flash-image",
contents=payload["contents"],
config=types.GenerateContentConfig(
http_options=types.HttpOptions(extra_body=payload),
),
)payload is the complete JSON body shown in the playground's Request view.
Read the response
{
"candidates": [{
"index": 0,
"content": {
"role": "model",
"parts": [
{ "text": "The coat is now dark green." },
{ "inlineData": { "mimeType": "image/png", "data": "<image bytes>" } }
]
},
"finishReason": "STOP"
}],
"usageMetadata": {
"promptTokenCount": 120,
"candidatesTokenCount": 1120,
"totalTokenCount": 1240
},
"modelVersion": "gemini-3.1-flash-image"
}Iterate through candidates[].content.parts[]. text contains text and inlineData contains Base64 image bytes. URI delivery replaces image parts with fileData while retaining text. Use mimeType to choose the file extension. Responses may also include responseId, safetyRatings, and finishMessage.
In usageMetadata, promptTokenCount counts input tokens, candidatesTokenCount counts output tokens, and totalTokenCount is the total. Thinking usage may appear in thoughtsTokenCount. These measure tokens, not currency or image count.
Completion and empty results
finishReason: "STOP" means a candidate ended normally. Still read its actual parts for images. Other common values:
| Status | Meaning |
|---|---|
MAX_TOKENS | Output token limit reached |
SAFETY / IMAGE_SAFETY | Text or image blocked by safety policy |
PROHIBITED_CONTENT / IMAGE_PROHIBITED_CONTENT | Prohibited content |
RECITATION / IMAGE_RECITATION | Content reproduction restriction |
NO_IMAGE | No image generated |
IMAGE_OTHER / OTHER | Another reason; inspect finishMessage |
If there are no candidates, inspect promptFeedback.blockReason and promptFeedback.safetyRatings. If candidates contain no image, read the text, finishReason, and finishMessage. HTTP 200 alone does not establish an image result.
Multi-turn editing
Retain the complete previous model content and append the next user turn. Preserve returned context fields such as thoughtSignature unchanged.
history = [
types.Content(role="user", parts=[types.Part(text="Create a coffee shop poster")]),
response.candidates[0].content,
types.Content(role="user", parts=[types.Part(text="Keep the layout; change the title to SUMMER MENU")]),
]
response = client.models.generate_content(
model="gemini-3.1-flash-image",
contents=history,
config=types.GenerateContentConfig(response_modalities=["TEXT", "IMAGE"]),
)The first history turn must match the original request that produced response. Continue appending model and user turns. For an independent edit of an existing picture, a reference-image request is sufficient.
More generation options
Depending on model support, add native top-level systemInstruction, safetySettings, and tools, or generationConfig.temperature, topP, maxOutputTokens, and thinkingConfig. For example:
{
"systemInstruction": {
"parts": [{ "text": "Create assets for one brand. Preserve supplied logos and brand colors." }]
},
"contents": [{
"role": "user",
"parts": [{ "text": "Design a spring promotion poster" }]
}],
"generationConfig": {
"responseModalities": ["TEXT", "IMAGE"],
"imageConfig": { "imageSize": "2K", "aspectRatio": "3:4" },
"temperature": 0.7
}
}systemInstruction sets overall requirements. temperature and topP control sampling. maxOutputTokens limits tokens, not pixels. safetySettings configures category thresholds, while tools depends on model support. For batches, submit independent requests rather than treating candidateCount as a universal image-count setting.
Streaming and errors
Use POST /v1beta/models/{model}:streamGenerateContent?alt=sse with the same JSON body. Each SSE data payload is a response fragment; process candidates, text, and images in order. It does not expose Studio queued / running job phases.
Non-streaming requests accept Idempotency-Key. Reuse it only with the same request; conflicts return 409. Streaming requests carrying the header return 400. Stored results are retained for 72 hours.
{
"error": {
"code": 400,
"status": "INVALID_ARGUMENT",
"message": "delivery must be INLINE or URI"
}
}For HTTP 400, check structure, enums, and MIME types; 401, check the key; 413, reduce the request; 429, check quota and concurrency; 500 / 502 / 504, inspect the error details. Errors after streaming starts appear in SSE JSON under error. For frequent timeouts, switch manually to the alternate host.


