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.

4 min readSume
All posts

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.

Submit errors and the retry advice (Sume docs, read 2026-10-05)
StatusCodeWhat to do
409idempotency_conflictSame key, different body. Use a new key for a new request
429queue_fullWait for jobs to finish or cancel queued jobs, then retry with the same key
429rate_limitedBack off, then retry with the same key
503provider_capacity_exceededRetry 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

All Developers posts

Written by Sume