Retry a lip-sync submit after a timeout without double billing
A timed-out submit may or may not have created a job. Resend the same body with the same Idempotency-Key and Sume returns the same job, not a second charge.

If a lip-sync submit times out, resend the exact same body with the exact same Idempotency-Key. Sume's lip-sync routes accept the header on both submit paths, and the docs say to reuse a key only for an exact retry, so a changed body returns 409 idempotency_conflict instead of a second job.
The problem a timeout creates
Your client sent the request and lost the connection. The job may exist, or it may not. If you resubmit without a key you risk two jobs, each with its own reservation. If you do nothing you risk no clip at all.
The rules
- Send Idempotency-Key on every paid submit that a client can retry.
- Use a key that names the work, such as a row id, not a random value made on every attempt.
- Retry the exact same body. A different body with the same key is a 409 idempotency_conflict.
- If you get 429 queue_full or 503 provider_capacity_exceeded, retry later with the same key.
Which errors are safe to retry
The admission doc lists these outcomes for generation submits.
| Status | Code | What to do |
|---|---|---|
| 409 | idempotency_conflict | Same key, different body. Use a new key for a new request |
| 429 | queue_full | Wait for jobs to finish or cancel queued jobs, then retry with the same key |
| 429 | rate_limited | Back off, then retry with the same key |
| 503 | provider_capacity_exceeded | Retry later with the same key |
A retry loop
This script tries up to three times with the same key and the same body. It retries only on a timeout or a 5xx, and prints the job id it gets back.
import json, os, time, urllib.error, urllib.request
body = json.dumps({
"avatar_handle": os.environ["AVATAR_HANDLE"],
"audio_url": os.environ["AUDIO_URL"],
"duration_seconds": 5.84,
}).encode()
headers = {
"Authorization": f"Bearer {os.environ['SUME_API_KEY']}",
"Content-Type": "application/json",
"Idempotency-Key": "lipsync-row-4821",
}
url = "https://api.sume.com/v1/minimax/h3-max/lip-sync"
for attempt in range(3):
req = urllib.request.Request(url, data=body, method="POST", headers=headers)
try:
with urllib.request.urlopen(req, timeout=15) as r:
print(json.load(r)["job"]["id"])
break
except urllib.error.HTTPError as e:
if e.code < 500:
raise
except TimeoutError:
pass
time.sleep(2 * (attempt + 1))What to do
Derive the key from your own record id and store it before the first request. After the retry returns, record the job id so a later worker can poll it instead of submitting again.
Sources
Related posts
More in Developers
- Idempotency-Key per shot: rerun one failed AI video shot in Python
One key per shot, derived from project, shot number and revision: a retry returns the same Sume job, a changed prompt gets a new key. Python key helper inside.
- Chain Ideogram 4.5 edits with webhooks: job.completed starts pass 2
Run a multi-turn Ideogram 4.5 edit chain on Sume without polling: submit with mode webhook, verify the signature, and start the next pass from job.completed.
- Ideogram 4.5 seed on Sume returns 400: how to repeat an edit
Ideogram's own API takes a seed for 4.5 edits, but Sume returns 400 unsupported_parameter for seed on every image model. Keep the output URL, not the seed.
- Ideogram 4.5 edit in TypeScript: handle 200, 202 and 502 on Sume
One fetch helper for POST /v1/images with Ideogram 4.5: return the URL on 200, poll status_url and result_url on 202, and throw the error body on 502.
Written by Sume