Retry a timed-out POST /v1/videos with one Idempotency-Key, pay once
A Python submit wrapper that retries network errors, 429 and 5xx with a stable Idempotency-Key, so a replay returns the original job instead of a second charge.

Send the same Idempotency-Key on every retry of the same POST /v1/videos, and a replay returns the original job rather than starting and billing a second one. Build the key from something stable in your own system, such as an order id and clip number, not a fresh UUID per attempt.
This matters most on timeouts. Your client cannot tell whether the request died before or after Sume created the job, and the docs are explicit that you must not submit duplicate paid work because a local call timed out.
The wrapper
It retries connection errors, timeouts, 429 and 5xx, honors retry-after, and raises immediately on other 4xx such as 402 or 400.
import os, time, requests
H = {"Authorization": "Bearer " + os.environ["SUME_API_KEY"]}
def submit(body, key, tries=5):
for n in range(tries):
r = None
try:
r = requests.post("https://api.sume.com/v1/videos", json=body,
headers={**H, "Idempotency-Key": key}, timeout=30)
except (requests.ConnectionError, requests.Timeout):
pass
if r is not None and r.status_code != 429 and r.status_code < 500:
r.raise_for_status() # 400/402/409 are not retried
return r.json()
wait = float(r.headers.get("retry-after", 0)) if r is not None else 0
time.sleep(max(wait, 2 ** n))
raise RuntimeError("gave up; resend with the same key: " + key)
job = submit({"model": "wan-3.0", "prompt": "Rain on a window, macro",
"duration": 6, "resolution": "480p"}, "order-8823-clip-1")
print(job["id"])The key rules
- Reuse a key only for the exact same operation and payload.
- The same key with a different body returns
409 idempotency_conflict. - Do not retry
402 insufficient_credits; no key can fix a short balance. - After a
queue_fullorprovider_capacity_exceeded, retry later with the same key.
What a duplicate would have cost
One six-second Wan 3.0 clip at 480p is $0.375 on Sume. A wrapper that mints a new key per attempt and retries five times after an ambiguous timeout could, in the worst case, create five jobs and reserve $1.875 for one intended clip. A stable key keeps it at one.
Choosing the key
A good key names the intent, not the attempt. order-8823-clip-1 stays the same across retries, restarts and even a different worker picking up the same item. A random UUID generated inside the retry loop does the opposite and defeats the whole mechanism. If you do generate a UUID, create it once, persist it with the work item, and read it back on every attempt.
| Key source | Survives a crash? | Safe to retry? |
|---|---|---|
| Order id plus clip number | Yes | Yes |
| UUID stored with the work item | Yes | Yes |
| UUID created inside the retry loop | No | No, each attempt is a new job |
| Timestamp | No | No |
Remember that a key is bound to its payload. If you edit the prompt and resend under the same key, you get 409 idempotency_conflict; that is the signal to mint a new key because the operation is now a different one.
Sources
Related posts
More in Developers
- Retry an avatar video submit without paying twice on Sume
Use one Idempotency-Key per paid avatar video submit and poll the job instead of resubmitting after a timeout. A Python example and the rules from Sume docs.
- Retry cost of a mixed project: what a failed shot, 402 and replay bill
Six failure cases for an image, voice, music, video and render project, with the cost of the retry for each and the one mistake that pays for everything twice.
- Review a 30-second Wan 3.0 clip: video inspect caps at 24 stills
Video inspect returns at most 24 stills per call, so a 30-second clip needs a sample rate of 0.8 fps or an explicit list of 24 times. Python builds the request.
- RMBG or upscale fails: image_not_fetchable and the HTTPS fix list
Sume cutout and upscale jobs need a public HTTPS image_url of at most 2,048 characters. Fix list for image_not_fetchable, 400 on model, 402 and 429 errors.
Written by Sume