Skip to main content
This page is for developers calling gemini-3-pro-image (Nano Banana Pro) via APIYI. It explains the output structure of the response JSON and what each usageMetadata field actually means, and clarifies several counting behaviors that look like anomalies but are inherent to the model. All conclusions come from testing against the production gateway (48 text-to-image + 18 image-edit requests), cross-checked against Google’s official docs (ai.google.dev/gemini-api/docs/image-generation), not speculation.

Overall Response Structure

APIYI’s nano banana series uses the Google native format. The response always has four top-level fields:
{
  "candidates":    [ ... ],          // generation results (image/text parts)
  "usageMetadata": { ... },          // token usage
  "modelVersion":  "gemini-3-pro-image",
  "responseId":    "..."
}

On successful generation

"candidates": [{
  "content": {
    "role": "model",
    "parts": [
      { "inlineData": { "mimeType": "image/jpeg", "data": "<base64>" } }
    ]
  },
  "finishReason": "STOP",
  "index": 0
}]
parts may contain more than one image. With complex task-style prompts (multi-constraint tasks such as “4-view character sheet”), the model may return multiple image parts in a single response (2–10 observed in testing) — they are interim drafts from the model’s “thinking process” plus the final version. Google’s docs state that “the last image within Thinking is also the final rendered image”, so just take the last one. Pure text-to-image and simple edits (add accessories / change background / restyle) usually return just 1. Either way, always iterate over parts and take the last inlineData when you only need one image. See Dev Guide · Why Do Responses Occasionally Contain Multiple Images for details.

When blocked by safety policies

The HTTP status code is still 200; the difference is inside the candidate:
"candidates": [{
  "content": { "parts": null },        // ⚠️ parts is null, not an empty array
  "finishReason": "IMAGE_SAFETY",      // or NO_IMAGE / PROHIBITED_CONTENT
  "finishMessage": "Unable to show the generated image. ...",  // only present in some cases
  "index": 0
}]
  • Three finishReason values were observed in testing: IMAGE_SAFETY (the output image violates policy), PROHIBITED_CONTENT (a prohibited-use policy was triggered, with an explanatory finishMessage), and NO_IMAGE (no image generated, usually returned within seconds).
  • The refusal explanation lives in the finishMessage field — it does not appear as a text part inside parts.
  • Your parsing code must handle parts being null, otherwise blocked responses will crash it.
For failure diagnostics, content-moderation policies, and user-friendly messaging strategies, see the Gemini Image Error Handling Guide.

usageMetadata Field Meanings

Successful generations always carry 6 fields:
"usageMetadata": {
  "promptTokenCount": 615,          // total input tokens (text + input images)
  "candidatesTokenCount": 2478,     // total output tokens (images + internal generation tokens)
  "thoughtsTokenCount": 208,        // thinking (reasoning) tokens
  "totalTokenCount": 3301,          // total billed amount for this request
  "promptTokensDetails":     [ { "modality": "TEXT",  "tokenCount": 99 },
                               { "modality": "IMAGE", "tokenCount": 516 } ],
  "candidatesTokensDetails": [ { "modality": "IMAGE", "tokenCount": 2240 } ]
}
FieldMeaningReliability
promptTokenCountInput-side total✅ Always equals the sum of promptTokensDetails
candidatesTokenCountOutput-side total✅ The billing figure; but greater than the sum of details — see Behavior 1 below
thoughtsTokenCountThinking tokens, typically 50–350 in testing
totalTokenCountGrand total✅ Always equals the sum of the previous three on successful generation; refusals are the exception — see Behavior 2 below
promptTokensDetailsInput breakdown by modality✅ Complete breakdown
candidatesTokensDetailsOutput breakdown by modality⚠️ Covers only the image portion — not a complete breakdown
Image tokens are determined by the resolution tier, not the aspect ratio: 1120 tokens per image at both the 1K and 2K tiers, 2000 per image at 4K. The aspect ratio only changes the pixel dimensions, never the token count. When N images are returned in one response, details equal exactly N × per-image value. The table below is Google’s official Pro Image aspect-ratio and image-size reference (source: ai.google.dev/gemini-api/docs/image-generation), fully consistent with our measurements of gemini-3-pro-image:
Aspect ratio1K size1K tokens2K size2K tokens4K size4K tokens
1:11024x102411202048x204811204096x40962000
2:3848x126411201696x252811203392x50562000
3:21264x84811202528x169611205056x33922000
3:4896x120011201792x240011203584x48002000
4:31200x89611202400x179211204800x35842000
4:5928x115211201856x230411203712x46082000
5:41152x92811202304x185611204608x37122000
9:16768x137611201536x275211203072x55042000
16:91376x76811202752x153611205504x30722000
21:91584x67211203168x134411206336x26882000
In Google’s official table, the column header 1K tokens means “the token count for the 1K resolution tier” — the actual per-image token count is the cell value: 1120 per image at 1K/2K, 2000 at 4K. (The Chinese localization of that page renders the header as “1,000 tokens”, which is easy to misread as the per-image count.) Also note the 512px tier (747 tokens per image) exists only for the Flash image models — gemini-3-pro-image supports 1K/2K/4K only.

Three Behaviors That Look Like Anomalies

Behavior 1: candidatesTokenCount ≠ sum of candidatesTokensDetails — normal and inevitable

In testing, 100% of samples (49/49 successful generations) showed candidatesTokenCount exceeding the sum of details by 88–630 tokens (the more complex the prompt and the more images returned, the bigger the gap). Reason: candidatesTokensDetails counts only the image payload itself (fixed 1120/2000 per image), while candidatesTokenCount also includes internal tokens generated alongside the image-generation process, which have no corresponding modality entry. This is Gemini’s native counting convention; APIYI passes it through unchanged.
Bottom line: do not treat details as a complete breakdown of candidatesTokenCount for validation. For reconciliation and billing, always use candidatesTokenCount / totalTokenCount; details are only useful for estimating the image share.

Behavior 2: totalTokenCount ≠ prompt + candidates + thoughts — only on responses with no image output

  • On successful generation, the equation holds strictly (49/49): total = promptTokenCount + candidatesTokenCount + thoughtsTokenCount.
  • On safety-blocked responses (no image output), the equation never holds (6/6), with a fixed pattern:
candidatesTokenCount == thoughtsTokenCount     // thinking tokens are written into both fields
totalTokenCount == promptTokenCount + thoughtsTokenCount   // total counts them once — this is correct
In refusal responses, candidatesTokenCount mirrors thoughtsTokenCount, so summing the three fields double-counts the thinking tokens. This is also upstream-inherent behavior. totalTokenCount itself is accurate — just use it directly. If about 10% of the responses in your logs “don’t balance”, check whether those responses have empty parts — they are almost certainly safety-blocked samples.

Behavior 3: output tokens occasionally hit 6000+ — caused by multiple image parts from the thinking process

Google’s official docs state that Gemini 3 image models are thinking models: “Thinking” is enabled by default and cannot be disabled in the API. The model generates interim images to test composition and logic, and “the last image within Thinking is also the final rendered image” (source: the Thinking Process section of ai.google.dev/gemini-api/docs/image-generation). In our testing, these interim thinking drafts come back in the native generateContent response as ordinary image parts: every part carries a thoughtSignature field but no thought: true flag, and each one is counted at 1120 tokens in candidatesTokensDetails. Google’s docs say Thinking generates at most two interim images, but with complex task-style prompts we observed up to 10 image parts in a single response. Usage grows strictly linearly with the image count:
Images returnedcandidatesTokensDetailscandidatesTokenCounttotalTokenCount
1 (text-to-image, 1K)1120~1210–1275~1350–1450
22240~2500~3300
33360~3800~4600
44480~5000~5900
55600~6200~7000
1011200~12700~13500
The thoughtsTokenCount field only counts text thinking and never exceeded 400 in testing — the source of high output tokens is the number of image parts, not this field. When you see 6000+ or even five-digit output tokens, check the number of parts in that response — it is almost certainly a multi-image response and normal billing (still reconcile with totalTokenCount).

Thinking Levels & the Two API Paradigms

How thinkingLevel affects tokens

Thinking-level control is only supported by Gemini 3.1 Flash Image / Flash Lite Image (generationConfig.thinkingConfig.thinkingLevel, default minimal, or high); on gemini-3-pro-image thinking is always on and cannot be adjusted. Measured (same prompt, 1K text-to-image, via the APIYI gateway):
Model / settingthoughtsTokenCountImage tokenstotalTokenCountLatency
gemini-3.1-flash-image · minimal (default)field absent1120~1534–1554~12–13s
gemini-3.1-flash-image · high700–7921120~2243–2375~18–23s
gemini-3-pro-image · high passed in181–214 (same as default range)1120~1427–1471~23s
  • high only increases thinking tokens and latency — image tokens stay unchanged (still 1120 per image).
  • Passing thinkingLevel to gemini-3-pro-image does not error, but has no measurable effect — thinking tokens stay in the default range.
  • includeThoughts: true changed neither the response structure nor billing in testing; Google states explicitly that thinking tokens are billed by default whether or not you view the thinking process.
  • Google also notes that “minimal thinking does not mean the model does no thinking at all” — under minimal the usage simply stops listing a separate thoughtsTokenCount field.

How image-model thinking tokens differ from text models

  • Text thinking models: the thinking output is text; thoughtsTokenCount can reach thousands and is billed at the output-token price. Officially, pricing is based on the full internal thoughts the model generates, even though the API only returns thought summaries (source: the pricing section of ai.google.dev/gemini-api/docs/thinking).
  • Image thinking models: thinking produces two kinds of output — a small amount of text thinking counted in thoughtsTokenCount (measured: up to 400 on Pro, ~800 on Flash at high), and interim draft images, which come back as ordinary image parts billed at 1120/2000 tokens each into candidatesTokenCount. So for image models the “cost of thinking” mostly shows up in the number of image parts, not in the thoughtsTokenCount field (see Behavior 3 above).

The two API paradigms

Google’s image-model docs now come in two flavors: the classic generateContent API (stateless) and the newly recommended Interactions API (built for agents and tools). The APIYI gateway uses the Google native generateContent format — everything in this page is based on it. The thinking-related differences:
generateContent (this page)Interactions API
Thinking-level parametergenerationConfig.thinkingConfig.thinkingLevelgeneration_config.thinking_level
Thought content in responseincludeThoughts switch (no visible effect for image models in testing; interim drafts always come back as ordinary image parts)returned explicitly as steps (type: "thought"), no includeThoughts switch
Usage field namesthoughtsTokenCount / candidatesTokenCount / totalTokenCounttotal_thought_tokens / total_output_tokens
For the full comparison of the two paradigms (endpoints, state management, data retention, and APIYI gateway compatibility tests), see Interactions API vs generateContent.

Parsing & Reconciliation Best Practices

data = resp.json()
cand = (data.get("candidates") or [{}])[0]
parts = (cand.get("content") or {}).get("parts") or []   # handles parts=null

images = [p["inlineData"]["data"] for p in parts if "inlineData" in p]
if images:
    final_image = images[-1]                  # last one is the final version
else:
    reason = cand.get("finishReason")         # IMAGE_SAFETY / NO_IMAGE / PROHIBITED_CONTENT
    message = cand.get("finishMessage", "")   # may be empty
  1. Reconcile billing with totalTokenCount (it is accurate even for refusals); do not validate by summing the three fields yourself or by summing details.
  2. Iterate over parts — never assume a single image; any per-image business logic should go by the actual number of inlineData parts.
  3. Handle blocked responses with parts = null + HTTP 200, branching on finishReason.
  4. Simple edits take ~22–25s; complex tasks (multi-image responses) take 35–142s, longer with more images. Set client timeouts to ≥ 5 minutes (including any proxy layer).

Nano Banana Dev Guide

Integration methods, input image requirements, billing basics, timeout settings, and the multi-image explainer

Error Handling Guide

The three key indicators for diagnosing failed generations, content moderation policies, and friendly prompt strategies

Failed Generation Guarantee Plan

For failures not caused by your input, credits are reimbursed per the number of failed requests

Nano Banana Pricing

Per-image pricing across resolutions and model tiers