When an AI image API automatically retries after a timeout or a 5xx error, it's easy for the same generation request to be billed twice as if it were a brand-new task. The correct approach is to attach an Idempotency-Key to every request, reuse the same key when retrying after a timeout or 5xx error, and only issue a new key for a genuinely new request. Flux Art's OpenAPI has made this mechanism a required field, and this article walks through the official response codes one by one to make clear which errors should be retried and which shouldn't.

First, Understand: Why Retries Cause Duplicate Charges
To understand this, you first need to grasp two basic design points of AI image generation APIs: charges happen the moment a task is created, and task creation is an asynchronous process. After a successful call to the generation endpoint, the server immediately returns 201 with the task status set to queued, and points are deducted at that exact moment; you then poll GET /tasks/{task_id} to get the final result. This design is fine on its own — the real issue is that "the client didn't receive a response" and "whether the server actually processed the request" are two completely separate things.
Breaking down the exceptions a client might encounter into three categories, since each is handled completely differently:
The first category is a network-layer timeout — the request packet may already have reached the server and successfully created a task, but the response was lost on the way back, or the client's wait time simply expired. In this case, if the client assumes "no response = failure" and resends the exact same request, the server will treat it as a brand-new business request, create another task, and deduct points again.
The second category is a server-side 5xx error, such as internal_error or service_unavailable. The meaning of this kind of error is "something really did go wrong on the server side," but whether the request was never processed at all or was processed halfway through is something the client can't tell from the error alone — the handling principle is the same as for timeouts.
The third category is a client parameter error, such as 400 invalid_request, 400 invalid_media_url, or 422 validation_error. The meaning of this kind of error is unambiguous: something is wrong with the request itself, and the server never actually entered the generation pipeline. Retrying with the same parameters will just keep returning the same error, with no change whatsoever.
The whole point of the Idempotency-Key is that, in the "outcome-uncertain" scenarios of the first two categories, it lets the server recognize "whether this retry is the same business request as the last one." As long as the key matches, the server will simply return the result of the previous task as-is, without creating a new task or deducting points again.
Which Errors Should Be Retried, and Which Shouldn't
Here's a table organizing the error codes the API actually returns by "whether to retry" and "how to handle the Idempotency-Key," so anyone taking over the project can write retry logic straight from this table instead of re-deriving the rules every time.
| Situation | Should You Retry? | How to Handle the Idempotency-Key | Rationale |
|---|---|---|---|
| Request timeout, no response | Yes, retry | Reuse the same key | The server may already have created the task; retrying with the same key returns the original result directly |
| 5xx internal_error / service_unavailable | Yes, retry — exponential backoff recommended | Reuse the same key | Temporary server-side fault; the same key guarantees the task isn't created twice |
| 429 rate_limit / concurrent_limit | Yes, retry — wait per Retry-After | Reuse the same key | Rate limiting doesn't mean the task failed; it's still the same business intent |
| 400 invalid_request / invalid_media_url | No, don't retry | Fix the parameters first, then decide whether a new key is needed | The parameters themselves are wrong; retrying as-is will just keep returning the same error |
| 422 validation_error | No, don't retry | Check the details field, fix the parameters, then send a new request | Failed validation automatically refunds the points (usage.points_refunded) |
| A genuinely new business request | — | A new key is required | Reusing an old key for a different request returns 409 idempotency_key_reused |

Which Situation Are You In? Find Your Match
| Your Scenario | The Most Painful Part | How to Handle It on Flux Art | Recommended Primary Model |
|---|---|---|---|
| An e-commerce script batch-generates hero images and occasionally hits network timeouts | After a timeout, you can't tell whether the task was actually created, and you're worried a retry will double-charge you | Generate an 8-128 character Idempotency-Key for every image request (concatenating the product SKU with a version number is the easiest approach). On timeout, retry POST /images/generations with the exact same key — the server recognizes the key and returns the original task result as-is, with an Idempotent-Replayed response header, without deducting points again | gpt-image-2 |
| A short-video team runs storyboard clips concurrently, and the script frequently hits 429s | Whether to retry after a rate-limit error, and whether a retry counts as a new task | On a 429, first read the Retry-After header and wait the specified number of seconds, then retry with the same Idempotency-Key using exponential backoff. Don't mistake a rate limit for a task failure and switch to a new key for a new request | doubao-seedance-2-0-260128 |
| The backend occasionally receives a 5xx and can't tell whether the server actually processed the request | Uncertain whether to retry, and whether retrying might create a duplicate task | 5xx errors are explicitly retryable — retry with the exact same Idempotency-Key used the first time, and the server uses that key to guarantee a single business intent only gets billed once. If you're still unsure, you can first call GET /tasks/{task_id} to check whether the task already exists | gemini-3-pro-image-preview |
| A wrong prompt or reference image URL is passed, and the API keeps returning 400 | The team's generic retry middleware blindly retries every error three times | 400 invalid_request / invalid_media_url is a parameter problem, not a retryable error. Fix the prompt or image_urls per the message field in the error body first — only then do you need a new Idempotency-Key. Don't let generic retry logic replay 4xx errors the same way it does everything else | Not model-specific |
| A batch job occasionally hits 422 validation_error, and the team worries points were charged for nothing | Unclear whether the points deducted at creation were actually refunded | Check the usage.points_refunded field in the response — a failed validation automatically refunds those points, no manual appeal needed. Fix the parameters per the details field, then send a normal new request; don't auto-retry on 422 | Not model-specific |

Five Practical Steps: Building Idempotency and Retries into Your Application Code
Step 1: Sign up for a Flux Art account. New users get 500 free points (enough for roughly 30+ GPT Image 2 generations, subject to what the official site currently offers). You can register through https://flux-art.ai, then upgrade to the Pro plan or above after logging in (Open API Support is included with the Pro / Max / Ultra plans).
Step 2: On the API Key page in the console, create a key that starts with fa_live_. Store it only in server-side environment variables or a secrets manager — never put it in frontend code, an app bundle, or a public repository. The API base URL is https://open-api.flux-art.ai/openapi/v1 — note that this is the only API domain; there is no .ai API endpoint. The console itself, however, is reachable through https://flux-art.ai.
Step 3: In your application code, generate a unique Idempotency-Key for every "user-initiated generation request" — 8 to 128 characters, using only letters, digits, periods, underscores, colons, and hyphens. In practice, a "business ID plus timestamp" or a plain UUID both work fine; attach it as a request header.
Step 4: Wrap a unified retry-decision layer around your calls: on a timeout, 5xx, or 429, retry with the same key (wait per Retry-After for 429s; use exponential backoff plus a bit of random jitter for 5xxs). On 400, 401, 402, 404, 409, or 422, never retry automatically — first pin down the problem using the code and message fields in the error body, then decide what to do next.
Step 5: After a successful retry, check whether the response headers include Idempotent-Replayed — if so, this call hit a previous task's result and didn't incur a new charge. Periodically pull the task list with GET /tasks and reconcile usage.points_charged against usage.points_refunded to confirm the numbers match your actual task count.
Self-Check Checklist
- Does every "user-initiated generation request" generate its own independent Idempotency-Key?
- Is the key between 8 and 128 characters, using only letters, digits, periods, underscores, colons, and hyphens?
- Do timeout and 5xx retries reuse the exact same key, instead of automatically switching to a new one?
- Do 429 retries read the Retry-After response header, instead of hardcoding a fixed wait time?
- Are 400, 401, 402, 404, and 422 all explicitly excluded from automatic retries?
- When you receive 409 idempotency_key_reused, do you check whether the same key was mistakenly reused across different requests?
- Have you checked the Idempotent-Replayed response header to determine whether a call hit a replay?
- During periodic reconciliation, do you verify actual charges using usage.points_charged and usage.points_refunded, rather than just looking at task counts?
- Does the API key exist only in server-side environment variables or a secrets manager, never appearing in frontend code, logs, or a public repository?
Being Honest About the Limits: What Idempotency Can't Solve
What an idempotency key guarantees is that "the same key won't be billed twice" — but it doesn't solve every retry-related problem. Flux Art currently hasn't published the specific enumerated values for the image size field, the full supported range for video duration, the quality enums for each model, or a concrete number for the account concurrency limit, and there's no published webhook or callback mechanism — for now, you can only poll GET /tasks/{task_id} for results. All of this should be verified against the current documentation on the official site / console, rather than guessing a number from experience and hardcoding it. Also, an idempotency key only guarantees that the API layer won't create a duplicate task due to a network retry — a product-level decision like "should this image actually be regenerated" still has to be made in your own application code; the API won't make that call for you.
Once this mechanism is properly in place, you won't have to scramble to double-check whether the points add up every time you hit a timeout or a stray 5xx during day-to-day integration testing. What actually deserves your attention are errors like 400, 401, 402, 409, and 422 — the "retrying won't help" kind. The sooner you separate these from the "should retry" errors in your code, the cleaner your billing will be. Sign up for Flux Art to claim 500 points and run through your idempotency and retry logic end to end — the official site is accessible at https://flux-art.ai, with specific perks subject to what's currently offered.