Nano Banana Image Guide
Use Nano Banana image models through the Gemini-compatible API. This page covers text-to-image, image-to-image and request parameters. Aspect ratios, reference-image limits and resolution support can differ by model; unconfirmed capabilities are not guaranteed across the family.
- Base URL —
https://api.llmnex.com - Hong Kong acceleration —
https://hk.llmnex.com - Protocol — Gemini
generateContent - Available models — determined by the model list for your key
- Resolution — confirm per model; output dimensions are not guaranteed to be identical across models
Overview
Use the Gemini-compatible request format with https://api.llmnex.com and an API key issued by this service. Configure output modalities and supported parameters as described below. For Hong Kong acceleration, use https://hk.llmnex.com with the same paths and authentication. https://www.llmnex.com is the console, not an API endpoint.
| Item | Value |
|---|---|
| Base URL | https://api.llmnex.com |
| Hong Kong acceleration | https://hk.llmnex.com |
| Image endpoint | POST /v1beta/models/{model}:generateContent |
| Streaming endpoint | POST /v1beta/models/{model}:streamGenerateContent |
| Response format | IMAGE examples below use base64 in inlineData; determine the image format from the response mimeType |
| Images per request | Examples handle one image; do not rely on candidateCount for multiple images |
| Usage statistics | Non-streaming responses include usageMetadata (token counts); see Response Structure |
A model name identifies the target and billing mode, not all of its capabilities. In particular, only the name gemini-nano-banana-2.1 and backend Nano Banana 2.1 are confirmed; ask the service provider about other capabilities.
Authentication
Use either authentication method below. With an official Google SDK, configure the base URL, API key and output modality according to that SDK. This guide provides HTTP examples; changing only the URL is not guaranteed to work with every SDK version.
Authorization: Bearer YOUR_API_KEYx-goog-api-key: YOUR_API_KEYKeep your key private
Keys look like sk-xxxxxxxx. Make requests from your server; do not embed keys in frontend code or client apps.
Quick Start
Image generation requires explicit IMAGE output
The backend supports TEXT mode for fast initial text responses. To generate images, generationConfig.responseModalities must explicitly include "IMAGE". Use "responseModalities": ["IMAGE"]. If omitted or set to TEXT only, Pro models may generate text instead of images.
curl -X POST "https://api.llmnex.com/v1beta/models/gemini-3-pro-image-c:generateContent" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
--max-time 300 \
-d '{
"contents": [
{ "parts": [ { "text": "a red ceramic teapot on a wooden table, soft daylight" } ] }
],
"generationConfig": { "responseModalities": ["IMAGE"] }
}'set -o pipefail
curl -fsS -X POST "https://api.llmnex.com/v1beta/models/gemini-3-pro-image-c:generateContent" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" --max-time 300 \
-d '{"contents":[{"parts":[{"text":"a red ceramic teapot"}]}],"generationConfig":{"responseModalities":["IMAGE"]}}' \
| python3 -c "import sys,json,base64;
d=json.load(sys.stdin);
candidates=d.get('candidates') or [];
parts=(candidates[0].get('content',{}).get('parts') or []) if candidates else [];
b=next((p.get('inlineData',{}).get('data') for p in parts if p.get('inlineData',{}).get('data')),None);
if not b: sys.exit('No image found in response; check responseModalities, model support and response content');
image=base64.b64decode(b,validate=True);
open('out.png','wb').write(image);
print('saved out.png')"Models & Resolution
The model suffix distinguishes billing modes, not fixed resolutions. Models with -c use per-request billing; models without -c use token-based billing. List available models first. Prices and actual charges are determined by the console and your account bill.
| Model family | Backend | Notes |
|---|---|---|
gemini-3-pro-image | Nano Banana Pro | Pro family; quality and latency vary by request, with no absolute ranking |
gemini-3.1-flash-image | Nano Banana 2 | Flash family; validate quality and latency for your workload |
gemini-nano-banana-2.1 | Nano Banana 2.1 | New Nano Banana model; consult model-specific capability information |
Naming examples (actual availability depends on the list for your key):
| Model name | Billing |
|---|---|
gemini-3-pro-image-c | Per-request |
gemini-3-pro-image | Token-based |
Old fixed-resolution-suffix models have been retired. Update the full model name in your requests; do not switch automatically without confirming the billing mode.
The -c Suffix: Choose Resolution in the Request
Models ending in -c do not lock the resolution. Set it using generationConfig.imageConfig.imageSize. The default is 1K when omitted.
{
"contents": [ { "parts": [ { "text": "a plain grey cube" } ] } ],
"generationConfig": {
"responseModalities": ["IMAGE"],
"imageConfig": { "imageSize": "4K" }
}
}The -c examples on this page use 1K, 2K or 4K for imageSize. Do not pass unlisted values such as 512 or rely on automatic fallback for invalid values. Confirm the supported range for your model first.
Models without -c
Models without -c use token-based billing. Confirm resolution-parameter support in the specific model's documentation. Do not reuse fixed-resolution rules from retired models.
About 4K
4K is a resolution tier, not a fixed width-and-height guarantee. Read the actual dimensions of the returned image. Do not hardcode 4096 × 4096 or assume identical dimension rules across models.
Request Body
The request follows the official Gemini format. In addition to contents, image requests must explicitly include "IMAGE" in generationConfig.responseModalities. A prompt asking for an image is not sufficient.
| Field | Type | Description |
|---|---|---|
contents[].parts[].text | string | Prompt; multiple text parts are joined with spaces |
contents[].parts[].inlineData | object | Embedded reference image; see Image-to-Image. Confirm image-count limits per model |
generationConfig.responseModalities | string[] | Must explicitly include "IMAGE" for images; use ["IMAGE"]. The backend supports TEXT, so omission or TEXT-only requests may make Pro return text |
generationConfig.imageConfig.aspectRatio | string | Aspect ratio, such as "16:9"; default 1:1 |
generationConfig.imageConfig.imageSize | string | 1K / 2K / 4K for -c models; confirm parameter support for models without -c |
{
"contents": [
{ "parts": [ { "text": "a lighthouse on a cliff at dawn, cinematic" } ] }
],
"generationConfig": {
"responseModalities": ["IMAGE"],
"imageConfig": { "aspectRatio": "16:9" }
}
}Aspect Ratios
Historical documentation lists 1:1, 16:9, 9:16, 4:3, 3:4, 3:2, 2:3, 5:4, 4:5, 21:9, 1:4, 4:1, 1:8 and 8:1. This is not a universal support list for current models. Confirm the target model's ratios before use.
The following are historical 1K examples with no model attribution. They are not output guarantees for current models; use the dimensions of the actual returned image.
| aspectRatio | Historical 1K dimensions | Typical use |
|---|---|---|
1:1 | 1024 × 1024 | Avatars, product images |
16:9 | 1376 × 768 | Landscape covers, video opening frames |
9:16 | 768 × 1376 | Vertical short videos, phone wallpapers |
4:3 | 1200 × 896 | Traditional photography |
21:9 | 1584 × 672 | Ultrawide banners |
4:5 | 928 × 1152 | Portrait social images |
Note
Do not extrapolate fixed 2K / 4K dimensions from these examples or rely on automatic fallback for unsupported ratios. Correct an unsupported parameter instead of assuming the service will ignore it.
Confirm extreme aspect ratios separately
Historical documentation marks 1:4, 4:1, 1:8 and 8:1 as unsupported by the gemini-3-pro-image family. Avoid these four ratios for Pro until reconfirmed. Ask the service provider about support and pixel-dimension rules for other models, including Nano Banana 2.1. Do not inherit scaling or error rules from old examples.
Image-to-Image
Add inlineData to parts for image-to-image requests. Describe the desired changes in text and supply reference images for content or style.
{
"contents": [
{
"parts": [
{ "text": "Turn this photo into a watercolor painting, preserving the composition" },
{
"inlineData": {
"mimeType": "image/png",
"data": "iVBORw0KGgoAAAANSUhEUg..."
}
}
]
}
],
"generationConfig": { "responseModalities": ["IMAGE"] }
}| Constraint | Value |
|---|---|
| Reference-image count | Examples use one; confirm multi-image support and limits per model |
| Formats | PNG / JPEG / WebP, declared by mimeType |
| Encoding | Raw base64, without a data:image/png;base64, prefix |
| Output resolution | Set through imageSize for -c models, independently of input dimensions |
Parameters without Effect
These compatibility limits come from historical documentation and have not been revalidated for every model. Do not rely on these parameters. Acceptance and identical results are not guaranteed across models.
| Parameter | Recorded behavior |
|---|---|
generationConfig.candidateCount | Examples handle one image; do not rely on this parameter for multiple images |
generationConfig.seed | Accepted, but reproducibility across requests is not guaranteed; the same seed and prompt may produce different images |
googleSearch in tools | No web access and no groundingMetadata in the response |
generationConfig.thinkingConfig | No effect and no thinking-token count in the response |
contents[].parts[].fileData (reference-image URLs) | Not read; embed reference images using inlineData instead |
Streaming (Not Recommended)
Prefer non-streaming :generateContent
The following describes historical IMAGE streaming behavior, not rechecked for every model. It does not cover fast initial TEXT responses or guarantee the same behavior for new models.
Images are generated as a whole, without intermediate images to stream. The endpoint waits for the complete image, then sends the final result in one SSE chunk. Its content matches the generateContent response body, except that usageMetadata is absent.
You incur SSE parsing costs without the usual streaming benefits:
- No earlier image preview: generation takes as long as non-streaming.
- No connection keepalive: the server sends no bytes during generation, so intermediary idle timeouts are not prevented.
- All base64 data occupies one
data:line, historically often 7–11 MB. Some SSE clients and reverse proxies limit line length, increasing truncation risk. - Errors are not SSE: they are returned as JSON error bodies, requiring handling of both formats.
- No
usageMetadata: use non-streaming if you need usage statistics.
Allow sufficient timeouts in the client and intermediaries. Increasing only the client timeout does not prevent proxy or gateway timeouts. See Timeouts & Latency below.
The endpoint remains for compatibility with existing code. Change the endpoint to :streamGenerateContent to receive SSE with lines beginning with data: .
curl -N -X POST "https://api.llmnex.com/v1beta/models/gemini-3-pro-image-c:streamGenerateContent" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" --max-time 300 \
-d '{"contents":[{"parts":[{"text":"a plain grey cube"}]}],"generationConfig":{"responseModalities":["IMAGE"]}}'Response Structure
This is an example of a successful non-streaming image response from gemini-3-pro-image-c with explicit responseModalities: ["IMAGE"]. It does not guarantee the structure of TEXT or error responses.
{
"candidates": [
{
"content": {
"role": "model",
"parts": [
{
"inlineData": {
"mimeType": "image/png",
"data": "iVBORw0KGgoAAAANSUhEUgAABAAA..."
}
}
]
},
"finishReason": "STOP",
"index": 0
}
],
"responseId": "ee2bcd924c5d",
"upstreamJobId": "d427bc37-0f6c-458b-93c9-d40a0b5412f7",
"usageMetadata": {
"promptTokenCount": 4,
"promptTokensDetails": [ { "modality": "TEXT", "tokenCount": 4 } ],
"candidatesTokenCount": 1120,
"candidatesTokensDetails": [ { "modality": "IMAGE", "tokenCount": 1120 } ],
"totalTokenCount": 1124
}
}For successful image generation, search candidates[0].content.parts for inlineData.data; do not hardcode parts[0]. Check the HTTP status first, then check candidates and image data. Report an error when no image is present instead of decoding or writing a file.
| Field | Type | Description |
|---|---|---|
candidates[] | array | Examples use the first candidate; check it exists |
candidates[].content.role | string | model in this example |
candidates[].content.parts[].inlineData.mimeType | string | image/png in this example; use the actual response value |
candidates[].content.parts[].inlineData.data | string | Base64 image data; check it exists before decoding |
candidates[].finishReason | string | STOP for normal image completion |
candidates[].index | integer | 0 in this example |
responseId | string | Service request ID, matching the X-Oneapi-Request-Id response header |
upstreamJobId | string | Upstream job ID; include it with responseId in issue reports |
usageMetadata | object | Token usage for this request; see below |
This example shows only an IMAGE result. The backend supports TEXT mode. If the output modality is omitted or TEXT only, Pro models may return text in parts[].text; not every successful response contains an image. Optional fields such as promptFeedback, modelVersion and safetyRatings depend on the actual response and are not required for image extraction.
usageMetadata
usageMetadata reports token usage in the official structure: promptTokenCount (prompt and reference images), candidatesTokenCount (generated images), totalTokenCount, and per-modality breakdowns in *Details. Usage fields and calculations may differ by model; do not estimate actual charges from a fixed token count per image.
Three notes
- Historical IMAGE streaming requests (
:streamGenerateContent) omitusageMetadata. This has not been confirmed for every model or TEXT mode. For usage statistics, prefer non-streaming and check the actual returned fields. - Historical documentation labels
promptTokenCountas an estimate. Accuracy and calculation rules have not been confirmed for every model; treat it as a reference. usageMetadatareports usage, not the billing mode. Models with-cuse per-request billing; models without-cuse token-based billing. Your account bill determines actual charges.
4K responses can be large
Historical 4K base64 response samples were about 25–28 MB. Actual size varies by model and image content. Check HTTP client and intermediary response-body limits, and allow time for transfer. The latency table below covers server generation time, not download. For 4K on a slow connection, use a 600-second timeout.
Timeouts & Latency
Generation is significantly faster than before. These typical times are references, not latency guarantees.
A 300-second client timeout is still recommended to allow for peak-time queues, retries and image transfer. Typical latency is not a maximum; shorter timeouts may interrupt requests.
| Tier | Typical server generation time |
|---|---|
| 1K | About 15–25 seconds |
| 2K | About 15–35 seconds |
| 4K | About 20–45 seconds |
Image transfer is additional; response sizes vary by model and image content. Upstream load may increase latency at peak times. The service already retries internally; avoid aggressive client retries. Correct parameter, authentication, permission and moderation issues first. See below for temporary-failure retry guidance.
Error Handling
| HTTP | Meaning | Action |
|---|---|---|
| 400 | Invalid body, such as missing or empty contents | Check the JSON structure |
| 401 | Invalid or expired key | Check the Authorization header |
| 429 | Request limited | Back off for 2–5 seconds and retry once; success is not guaranteed; avoid retry loops |
| 451 | Content blocked by safety checks | Terminal; do not retry unchanged. Modify the prompt or reference image |
| 500 | Temporary upstream unavailability or internal gateway error | Retry once after a short delay; contact us if it persists |
| 503 | model_not_found — incorrect name or no permission for the model | See below |
| 503 | Other messages, such as upstream session error | Temporary upstream connection failure; retry once after backoff |
Keep the X-Oneapi-Request-Id response header and include it in reports when present. Never disclose your API key.
{
"error": {
"message": "image_unsafe: The generated images appear to be unsafe.",
"type": "invalid_request_error"
}
}Do not automatically retry 451
451 indicates a content-safety block. Change the prompt or reference image rather than automatically repeating the request. Repeated requests are not guaranteed to receive the same moderation outcome, and this does not determine whether you are charged.
503 No available channel for model ...
Check spelling, retirement status and your key's permissions first. Use GET /v1/models to confirm the available name instead of automatically retrying. Contact the service provider if the model is missing. If listed but still failing, include the request ID for investigation; this message alone does not rule out a service-side issue.
Complete Examples
The Python examples require requests; the Node.js example requires a runtime with built-in fetch. These examples save a PNG response as out.png. If the actual mimeType differs, use the matching extension. A successful write overwrites any file with that name in the current directory; back it up first.
import base64, requests
API_KEY = "YOUR_API_KEY"
BASE = "https://api.llmnex.com"
MODEL = "gemini-3-pro-image-c"
resp = requests.post(
f"{BASE}/v1beta/models/{MODEL}:generateContent",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"contents": [{"parts": [{"text": "a lighthouse on a cliff at dawn"}]}],
"generationConfig": {"responseModalities": ["IMAGE"], "imageConfig": {"aspectRatio": "16:9", "imageSize": "2K"}},
},
timeout=300, # Allow time for queues, retries and transfer
)
resp.raise_for_status()
candidates = resp.json().get("candidates") or []
parts = (candidates[0].get("content", {}).get("parts") or []) if candidates else []
b64 = next((p.get("inlineData", {}).get("data") for p in parts if p.get("inlineData", {}).get("data")), None)
if not b64:
raise RuntimeError("No image found in response; check responseModalities, model support and response content")
image = base64.b64decode(b64, validate=True)
with open("out.png", "wb") as f:
f.write(image)
print("saved out.png")import { writeFile } from "node:fs/promises";
const API_KEY = "YOUR_API_KEY";
const MODEL = "gemini-3-pro-image-c";
const res = await fetch(
`https://api.llmnex.com/v1beta/models/${MODEL}:generateContent`,
{
method: "POST",
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
contents: [{ parts: [{ text: "a lighthouse on a cliff at dawn" }] }],
generationConfig: { responseModalities: ["IMAGE"], imageConfig: { aspectRatio: "16:9", imageSize: "2K" } },
}),
signal: AbortSignal.timeout(300_000), // 5 minutes
}
);
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
const payload = await res.json();
const parts = payload.candidates?.[0]?.content?.parts ?? [];
const b64 = parts.find((p) => p.inlineData?.data)?.inlineData.data;
if (!b64) throw new Error("No image found in response; check responseModalities, model support and response content");
await writeFile("out.png", Buffer.from(b64, "base64"));
console.log("saved out.png");import base64, requests
src = base64.b64encode(open("input.png", "rb").read()).decode()
resp = requests.post(
"https://api.llmnex.com/v1beta/models/gemini-3-pro-image-c:generateContent",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"contents": [{"parts": [
{"text": "Turn this photo into a watercolor painting, preserving the composition"},
{"inlineData": {"mimeType": "image/png", "data": src}},
]}], "generationConfig": {"responseModalities": ["IMAGE"]}},
timeout=300,
)
resp.raise_for_status()
candidates = resp.json().get("candidates") or []
parts = (candidates[0].get("content", {}).get("parts") or []) if candidates else []
b64 = next((p.get("inlineData", {}).get("data") for p in parts if p.get("inlineData", {}).get("data")), None)
if not b64:
raise RuntimeError("No image found in response; check responseModalities, model support and response content")
image = base64.b64decode(b64, validate=True)
with open("out.png", "wb") as f:
f.write(image)