Overview
gemini-3-pro-image-preview (i.e., Nano Banana Pro) enforces strict content safety controls and will reject non-compliant requests at multiple layers. A simple “generation failed” message doesn’t help users understand the problem. Good error handling needs to:
- Accurately identify the rejection reason — distinguish between content violations, knowledge-base limits, and technical errors
- Provide friendly user messaging — turn technical errors into understandable explanations
- Offer actionable suggestions — tell users how to adjust their request to succeed
- Retain complete technical details — for developer debugging
When a request returns HTTP 200 but no image, this is usually a safety judgment made on Google’s side. APIYI’s transparent proxy simply forwards the result as-is — we want our customers to generate images successfully too. The detection and messaging logic must be implemented on your application side.
Google Content Moderation Policy (2026 Update)
Google’s image generation uses a two-layer safety mechanism:- Configurable filters: cover four categories — harassment, hate speech, sexually explicit content, and dangerous content — adjustable via
safetySettings - Built-in protections: always active for core harms (such as child safety) and cannot be disabled via parameters
- Generative AI Prohibited Use Policy:
policies.google.com/terms/generative-ai/use-policy - Generative content common errors reference:
ai.google.dev/api/generate-content
Three Core Diagnostic Indicators
Check in order of priority, from highest to lowest:1. candidatesTokenCount (highest priority) ⭐
- Location:
response.usageMetadata.candidatesTokenCount - Meaning: the token count of candidate content generated by the API
- Rule: a value of
0means the request was rejected outright at the content moderation stage — no candidate content was even generated. This is the strictest rejection.
2. finishReason (second priority)
- Location:
response.candidates[0].finishReason - Rule: any value other than
STOPindicates an abnormal completion that requires special handling
finishReason values (note that the Nano Banana series added image-specific values with the IMAGE_ prefix):
| finishReason | Meaning | User-friendly message |
|---|---|---|
STOP | Normal completion | - |
IMAGE_SAFETY | Output-side image safety filter | Content triggered the image safety policy |
PROHIBITED_CONTENT / IMAGE_PROHIBITED_CONTENT | Prohibited content | Content violates the safety policy and was rejected |
SAFETY | Safety filter | Content triggered the safety filter |
RECITATION / IMAGE_RECITATION | Citation/copyright restriction | Content may involve a copyright issue |
IMAGE_OTHER / NO_IMAGE | No image/other | No image could be generated; please adjust your prompt and retry |
MAX_TOKENS | Length exceeded | Content length exceeds the limit |
3. Text rejection explanation (important)
- Location:
response.candidates[0].content.parts[].text - Rule: when
finishReasonisSTOPbutpartscontains onlytextand no image data, the API has returned a rejection explanation rather than an image. The text may be in Chinese or English, for example:
Error Scenario Quick Reference
| Scenario | Detection condition | Typical cause |
|---|---|---|
| Content moderation rejection | candidatesTokenCount === 0 | Prompt/reference image contains sensitive content; earliest-stage rejection |
| Rejection during generation | finishReason !== 'STOP' and parts is empty | Prohibited content, safety filter |
| Text rejection explanation | finishReason === 'STOP', has text but no image | Sexually explicit content, non-compliant request |
| Knowledge-base limit | text mentions a future year (2026+) or an unreleased product | Knowledge base updated through January 2025 |
| Prohibited feature | text contains keywords like watermark, faceswap, re-dressing, celebrity, etc. | Prohibited features such as watermark removal/face swap/re-dressing/celebrities |
Handling Flow (Decision Order)
Code Implementation (Core)
Combine the checks above into a single parsing function:Consumer-Friendly Messaging
Design principles: clear and concise, positive guidance, actionable, no blame. Recommended templates:- Consumer users: by default show only the friendly explanation + revision suggestion
- Business / tool providers: by default expand technical details (
finishReason,candidatesTokenCount, etc.) - Developers: provide an “expand/collapse” toggle to view the full JSON response
Best Practices
- Check strictly by priority:
candidatesTokenCount→finishReason→parts→ extract data → keyword detection - Collect text before checking thoughtSignature to avoid losing the rejection explanation
- Keep the full response: development/testing tools should always save the raw JSON for troubleshooting
- Support Chinese and English rejection text: Google may return Chinese or English, so keyword matching must cover both
- Graceful degradation: give a specific message when smart detection succeeds; otherwise show the API text directly; otherwise use the friendly
finishReasonname; and only then fall back to a generic message - Never show “unknown error”: always include an actionable suggestion or the full response
FAQ
Why does the same prompt sometimes succeed and sometimes fail?
Why does the same prompt sometimes succeed and sometimes fail?
Google’s safety filtering has randomness and context dependence: reference image content and how the prompt is combined all affect the judgment. Try adjusting the wording or using more indirect phrasing.
How do I tell whether it's a content problem or a technical problem?
How do I tell whether it's a content problem or a technical problem?
candidatesTokenCount: 0 or finishReason: PROHIBITED_CONTENT → content problem; Failed to fetch or an HTTP error → technical problem; an API text explanation → usually a content problem.How much technical information should consumer users see?
How much technical information should consumer users see?
Tiered display: by default show the friendly explanation + revision suggestion; optionally expand technical details; in development mode show the full JSON response.
Do I need to write separate handling for every finishReason?
Do I need to write separate handling for every finishReason?
No. A mapping table plus a generic fallback is enough:
reasonMessages[finishReason] || then display the raw value.