Images idempotency and original-submission recovery
Original image responses are retained for 10 hours. Recover a lost response without generating another image.
Images idempotency and original-submission recovery
Send Idempotency-Key on the first generation request. A key cannot be attached retroactively to an earlier submission. If its response is lost, recover the original submission using authenticated GET requests; do not create a replacement generation.
Coverage
The contract applies to every current or future model served through POST /v1/images/generations, /v1/images/generations/async, /v1/images/edits, /v1/images/edits/async, /v1/edits, and /v1/images/variations when that operation is supported. It also covers the /v1/midjourney/generations aliases and legacy /mj/submit/* routes. It does not enable an otherwise unsupported model/endpoint combination.
To cover image-producing multimodal models without a model allowlist, the same protection applies to keyed POST /v1/chat/completions, /v1/responses, and native Gemini /v1/models/{model}:generateContent calls (including :predict and :streamGenerateContent). These routes can also produce text; their keyed calls follow the same contract. Streaming output is buffered for durable recovery rather than delivered incrementally. Prefer non-streaming calls with this contract.
Requests without this header retain their existing behavior. Seedance has a separate video submission contract.
Header, scope and request identity
Authorization: Bearer YOUR_API_KEY
Idempotency-Key: image-order-123-attempt-1
Content-Type: application/json
- Use 8–128 visible ASCII characters, with no spaces. Generate a unique key for each intended generation and retain it before sending the request.
- Keys belong to the submitting account, across all covered endpoints. Another valid API key for the same account can recover the receipt and original response. Account status, key revocation/expiry and IP restrictions still apply. Exhausted key quota does not block recovery; a new execution still requires normal quota authorization.
- APIMaster and other services have independent key namespaces and receipts. Reusing a key on another service does not deduplicate across services.
- A repeated key with identical request identity never executes or charges again. An unexpired accepted response is replayed with
Idempotency-Replayed: true. An expired accepted response returns HTTP 410submission_response_expiredwith the original receipt, without executing again. If the outcome is pending or unknown, it returns HTTP 202 and the original receipt. - A changed endpoint, model, body, effective key group or explicitly pinned channel returns HTTP 409 with
error.code = idempotency_conflict. JSON identity is byte-exact: retain and resend the original bytes, including whitespace, field order, number spelling and omitted/explicit fields. GET recovery requires no body. - For multipart, the boundary and uploaded filename may change. Form field values, upload field names, file bytes and file order within each upload field must remain identical. Repeated scalar fields are rejected. Do not change references while reusing a key.
- Original response bodies are retained for 10 hours after persistence, then automatically cleared. This policy also applies to historical responses. Receipts, request fingerprints, execution and billing state remain stored; keys are not automatically reused. Expiration never authorizes another execution. Stored response URLs retain their own lifetime; download results promptly.
Recovery preserves the original HTTP status and complete response, including Base64 image fields; no separate storage link is needed. If recovery temporarily returns HTTP 503 submission_storage_unavailable, retry the same read-only lookup instead of creating another generation. Expired responses still return HTTP 410.
A durable account/key claim is written before downstream generation or billing. Concurrent requests use one original execution. Keyed submissions disable automatic channel retries and speculative image hedges; an uncertain attempt is not replaced internally. This is opt-in: requests without Idempotency-Key retain the existing fallback behavior and do not receive this original-submission recovery guarantee.
Recover without another generation POST
Use the same key and a valid Bearer key for the submitting account:
# Read the original submission receipt. This does not poll a provider or bill.
curl --fail-with-body 'https://apimaster.ai/v1/image-submissions/by-key' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Idempotency-Key: image-order-123-attempt-1'
# Retrieve the stored original HTTP body and status: task response or image result.
curl --fail-with-body 'https://apimaster.ai/v1/image-submissions/by-key/response' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Idempotency-Key: image-order-123-attempt-1'
Both lookups are authenticated and read-only: they do not create a task, generate an image, settle/refund billing or re-send to an upstream. They include Cache-Control: no-store, X-Image-Submission-Id and X-Original-Request-Id. Receipt responses use data; /response returns the original body and HTTP status when available, HTTP 202 with the receipt while not ready, or HTTP 410 with error.code = submission_response_expired and the original receipt after expiration. response_available indicates current body availability; response_expired and response_expires_at expose expiration state and time. Expiration does not mean the request was not executed or is safe to resubmit.
If the original response contains data[0].task_id, continue using the model's existing task GET endpoint with that ID and original model. The original response can instead contain synchronous data[].url / b64_json, a Midjourney batch or a native multimodal result. The recovery endpoint does not convert result formats or refresh expired image URLs.
Acceptance, billing and permission to retry
data.status |
Meaning | Create a replacement? |
|---|---|---|
pending, submitting |
The original execution is in progress | No; query the original receipt |
accepted |
The successful original response was persisted; an async task may still be running | No; retrieve its response and query its task |
acceptance_unknown |
Upstream acceptance cannot be confirmed | No; wait for a final retry decision or contact support |
not_accepted |
Rejected before entering generation/business/billing code | Correct the cause and use a new key when the safe-retry fields below are true |
closed_retry_allowed |
The platform closed the uncertain image request after verifying zero reservation or completing its refund | Yes, use a new key; the original request has zero final net charge |
Use new_submission_safe = true, retry_safe = true, billing_final = true and net_charge = 0 together to decide whether to start a replacement. For not_accepted, acceptance_final = true and billing_status = final_zero. For closed_retry_allowed, acceptance_final = false, upstream_acceptance = unknown, and billing_status is refunded or final_zero. Permission to retry is a platform billing guarantee; it does not prove the upstream never executed the original request. APIMaster bears any later supplier cost for a closed request and does not charge that original request again.
The closure policy covers all models on the image generation/edit/variation and Midjourney endpoints. Mixed text/image APIs qualify through configured image pricing or an explicit image output modality / image-generation tool. Pure text requests are excluded. Send Idempotency-Key on the original request to obtain a durable receipt.
For a completed uncertain image request without a known task ID, the default verification window is 10 minutes after the original response is persisted. A background worker checks about once a minute. It consults available upstream billing evidence and existing holds, completes any required refund, then closes the receipt only after durable zero-charge evidence exists. This is not a guaranteed ten-minute completion deadline: database/refund errors or missing billing evidence leave retry_safe = false. Historical records without billing evidence, interrupted executions without a complete response, and known async tasks do not become retryable merely because time passed. Keep polling the known task when one exists.
{
"data": {
"status": "closed_retry_allowed",
"acceptance_final": false,
"upstream_acceptance": "unknown",
"retry_safe": true,
"new_submission_safe": true,
"billing_status": "refunded",
"billing_final": true,
"net_charge": 0,
"next_action": "retry_with_new_key"
}
}
For pending/unknown states, billing_status = unverified, billing_final = false, and net_charge = null. A positive retry_after_seconds indicates the remaining initial verification window, not a guarantee that closure will succeed when it reaches zero.
The old key remains bound to the old receipt forever. Reposting the identical request with that key returns HTTP 202 and the closed receipt, without generation or billing. Use a new key for the next intended attempt. /response returns the stored original HTTP body/status during its 10-hour retention, which can still be 500 or 524; after expiration it returns 410 submission_response_expired; query /by-key for the current retry decision. Neither GET endpoint triggers generation, reconciliation or refunds.
An absent receipt returns HTTP 404 with error.code = submission_not_found and error.retry_safe = false; it is not not_accepted or permission to retry. Query the same service and submitting account and send the exact original key in the Idempotency-Key header, not as a query parameter. Another valid API key for the same account can recover it. If 404 persists, retain the original request ID for support.
Structured errors and uncertain outcomes
error.code |
Meaning | Safe to create a replacement? |
|---|---|---|
invalid_idempotency_key |
Header does not meet the format requirements | Correct the header; it does not establish another original request's outcome |
idempotency_conflict |
The account/key already identifies a different request | No; recover the original key |
submission_not_found |
No receipt is currently visible | No |
submission_storage_unavailable, submission_claim_unavailable |
Temporary storage/claim failure | No; retry the GET lookup for the original key |
submission_response_expired |
Original response body expired after 10 hours; execution and billing state remain queryable | No; the same key never executes again |
submission_response_too_large |
The original response exceeded the 64 MiB recovery buffer | No; use a known original task ID or contact support |
do_request_failed, read_response_body_failed, image_generation_timeout |
Transport, response-read or generation-wait failures on applicable adapters | No; these codes do not prove non-acceptance |
get_channel_failed |
Channel selection failed; this can also happen while selecting a fallback after an earlier attempt | No universal guarantee; recover the original receipt |
model_not_found with no non-exclusive channel available |
No route matched the model, request capabilities and provider preferences at selection time; this message does not necessarily mean the model ID is invalid | The code alone does not establish final acceptance or billing; recover the original receipt |
image_channels_exhausted |
All eligible image routes were exhausted after at least one attempt; the original provider error remains in usage logs | No; a provider may already have received the request |
Provider-specific errors and HTTP 408/429/5xx are not a universal safe-retry signal. After a lost response, preserve the account, key, original request bytes, UTC timestamp, model, endpoint and request ID if received. Never send your API key to support.
An error usage row with quota: 0 describes that row; it does not prove that an upstream created no task or that every reservation/settlement is final. In particular, an upstream HTTP 500 or 524 must not be converted to not_accepted solely because no task ID was returned. HTTP 200 from the receipt lookup can still contain acceptance_unknown.
Signed-in image playground proxy
The signed-in /api/playground/image proxy forwards a supplied Idempotency-Key and the original receipt headers. Recover through GET /api/playground/image with that header; add ?submission_response=1 to retrieve the stored original body. A GET with task_id remains a task query. Session authentication is required. The playground does not invent a key for a request that omitted it.
