TROUBLESHOOTING · ERROR FAQ
Image Generation API Content Filter Errors: Handling & Workarounds
In production, an image API's content filter is not an inconvenience — it is a failure mode your code must handle. Different APIs report the same rejection differently: some as HTTP 4xx status codes, some as a 200 with an error object in the body, some as a queued task that fails later. This guide compares the shapes, lays out a language-agnostic handling pattern around {code, retryable, action}, shows a batch-pipeline design that skips-and-logs instead of dying on item 3, and covers prompt pre-cleaning. Model pages for the two ends of the spectrum: FLUX.2 Pro for text-to-image and Nano Banana Pro for editing.
How APIs report filter rejections: three shapes
- HTTP 4xx at submit. Cleanest shape: 400/422 with a body naming the filter code. Easy to branch on with a status-code switch.
- HTTP 200 with error body. Common on async and proxy-style APIs: check the body's code field before treating the response as success — the classic 200-but-error trap.
- Task fails during polling. Submit succeeds, then the poll returns a terminal FAILURE state with a reason code. Requires handling at the polling stage, not just submit.
The handling pattern: {code, retryable, action}
Normalize every rejection into three fields: what happened (code), whether an identical retry can succeed (retryable), and what to do next (action). Filter rejections are rarely retryable — the fix is a rewrite. Rate limits are retryable — the fix is backoff. Billing and auth errors are neither — the fix is alerting a human. Gate your retry loop on the retryable flag, route non-retryable rejections to a rewrite queue, and log code frequencies: a spike in one filter code is a product signal (a phrasing pattern in your prompts), not just an ops metric.
def handle_image_result(resp):
err = resp.get("error") # present on filter hits even with HTTP 200
if not err:
return resp["url"] # result URL is the top-level url field
code, retryable = err["code"], err["retryable"]
if code.startswith("CONTENT_FILTER"):
return enqueue_rewrite(resp["prompt"]) # not retryable as-is
if retryable:
return retry_with_backoff(resp)
raise FatalAPIError(code, err["action"]) Batch pipelines: skip and record, don't halt
A 500-image batch will contain a few filter rejections; treat them as expected output, not exceptions. The pattern that survives production: write a per-item record (prompt, code, action) for rejects, continue the batch, then handle rejects in bulk — auto-rewrite the ones whose code suggests phrasing, flag the rest for review. Never wrap the whole batch in a single retry loop: one persistent rejection becomes an infinite loop with a bill attached. On ModAPIS, filter-rejected generations are not charged, so skipping costs nothing but time.
Prompt pre-cleaning that measurably helps
- Swap film jargon for plain verbs ("shoot" → "photograph").
- Replace named people with role descriptions.
- Keep prompts under ~60 words; risk accumulates with length.
- Strip template boilerplate you don't need this run — every clause is reviewed.
- For user-generated prompts, maintain a substitution map of your app's common trip-words.
What ModAPIS guarantees at the filter boundary
Every error — filter included — returns the same structure with code, retryable, and action fields, across all models, so one handler covers the catalog. Failed generations are not charged, in any tier. The quickstart has a first request running in five minutes; the model pages for FLUX.2 Pro and Nano Banana Pro list current per-output pricing in credits.