Retry a failed image batch on Sume: not billed, same Idempotency-Key
On Sume a failed image generation is not billed, but a retry after a timeout can double-submit. Send an Idempotency-Key per image and retry only the submit.

On Sume, a failed image generation is not billed, and failed requests return 502. A timeout is different: your client does not know whether the job ran. Send an Idempotency-Key that is stable per image, retry the submit with the same key, and the retry returns the original job instead of billing a second one. Do not submit a new paid job for the same intent.
What the docs promise
The Image API page says generation billing is all-or-nothing: a generation is either completed and billed in full, or it fails and is not billed. Failed or cancelled generations are not billed, and requests that end early because the client disconnected are billed as failed generations, meaning not at all.
Jobs and results adds the retry rule: when the wait expires the response is still 2xx, you must continue with GET status_url, you must not submit a new paid job for the same intent, and retrying the submit itself is fine if you reuse the same Idempotency-Key.
| What you see | Billed? | Next step |
|---|---|---|
| 200 with data[].url | Yes, completed | Store the URLs |
| 202 job envelope | Not yet | Poll status_url, then fetch result_url |
| 502 Bad Gateway | No | Retry the submit with the same key |
| Client timeout or disconnect | Not for a failed call | Retry the submit with the same key, then poll |
A key per image, not per run
Derive the key from your own record: a row id plus the variant number, such as listing-4821-v2. That way a rerun of the whole batch after a crash re-submits the same keys, and Sume returns the existing jobs for images that already went through. A random key per attempt defeats the purpose.
Use async mode for batches so each submit returns fast and the wait lives in your poller. For hundreds of images prefer mode: "webhook" and handle the terminal event on your server, verifying the signature as described in Webhooks.
A safe submit loop
The function below retries only the submit and keeps the key fixed.
import os
import time
import requests
H = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}
def submit(key: str, prompt: str, tries: int = 3) -> dict:
last = None
for attempt in range(tries):
try:
r = requests.post(
"https://api.sume.com/v1/images",
headers={**H, "Idempotency-Key": key},
json={"model": "openai/gpt-image-2.5", "prompt": prompt,
"quality": "medium", "mode": "async"},
timeout=30,
)
r.raise_for_status()
return r.json()["data"]
except requests.RequestException as err:
last = err
time.sleep(2 ** attempt)
raise RuntimeError(f"submit failed for {key}: {last}")
print(submit("listing-4821-v1", "A linen tote bag on a hook")["status_url"])Limits
Reusing a key with a different payload is the mistake to avoid: the docs say to reuse the same key only for the same operation. Check the Errors and credits page for what a low balance does to a batch.
Sources
Related posts
More in Developers
- Retry hints in the body or a header: reading Sume's Retry-After
Notion repeats Retry-After in response bodies. Sume can send a retry-after header on 429s. Here is how to retry each Sume error code safely.
- Retry-on-timeout wrapper from the Sora days? Add an idempotency key
A wrapper that retries a video submit on timeout can create two paid Sume jobs. Build an Idempotency-Key from the request, and learn what a replay returns.
- Retry policy by Sume job error category
Sume failed jobs carry a category and retryability. Retry queue and capacity errors with a cap, stop on validation and quota, and skip blanket retries.
- Expiring API keys: Sume key metadata and rotation habits
OpenAI added enforced key lifetimes in Sep 2026. Sume's docs list key id, name, prefix, scopes and last-used time, so rotate on a schedule you keep.
Written by Sume