Retry a timed-out video submit without paying twice: Idempotency-Key

Your client timed out after submitting a 30-second video job? Retry with the same Idempotency-Key and Sume returns the original job, not a second bill.

5 min readSume
All posts

Send the same Idempotency-Key header with the same body on the retry. Sume then returns the original job rather than creating and billing a second one. A 30-second clip at 1080p is not cheap, so a retry loop without a key is a way to pay for the same video twice.

The docs say it directly: when a sync wait ends or the network fails, you can retry the submit itself, and you must use the same key so the retry returns the original job.

What counts as the same request

Use the same key only for the same operation and payload. If you reuse a key for a different body, the API answers 409 idempotency_conflict. That error is useful, because it flags a key that is being generated from the wrong thing.

Derive the key from your own business id, not a random value made on each attempt. An order id plus a version number works. A random UUID created inside the retry loop defeats the point.

Which errors to retry with the same key (read 2026-10-07)
Status and codeMeaningWhat to do
429 rate_limitedRequest volume over the limitWait for retry-after, then retry with the same key
429 queue_fullAccepted-job capacity usedWait for a job to finish or cancel one, then retry with the same key
503 provider_capacity_exceededDispatch queue fullRetry later with the same key
409 idempotency_conflictSame key, different payloadFix the key, do not retry
402 insufficient_creditsBalance cannot cover the reservationUpgrade the plan or submit a cheaper request

A retry wrapper

This wrapper retries only on network errors and the retryable statuses above, and always resends the identical key.

import os, time, requests
URL = "https://api.sume.com/v1/video-router/generate"
def submit(body, key, tries=5):
    h = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}",
         "Idempotency-Key": key}
    for n in range(tries):
        try:
            r = requests.post(URL, headers=h, json=body, timeout=30)
        except requests.RequestException:
            time.sleep(2 ** n)
            continue
        if r.status_code in (429, 503):
            time.sleep(float(r.headers.get("retry-after", 2 ** n)))
            continue
        r.raise_for_status()
        return r.json()["data"]["request_id"]
    raise RuntimeError("submit failed after retries")
print(submit({"model": "wan-3.0", "prompt": "Rain on a neon street",
              "duration": 30, "resolution": "480p"}, "order-1042-v1"))

Store the job id as soon as you have it

The key protects the submit. The job id protects everything after it. Write the id to your database before you start waiting, so a restarted process picks the job up with GET /v1/jobs/{id}/status and never reaches the submit again.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume