Count Sume job replays with idempotency_hit after a client retry

A Sume job submit returns data.idempotency_hit. Log it after a retried video submit to prove the second call replayed the first job, not a new one.

5 min readSume
All posts

Read data.idempotency_hit on the submit response. It is a required boolean in the job submit schema: false for a new job, true when your Idempotency-Key matched an earlier submit. Counting the true values after retries is the cheapest proof that a timeout did not create a second paid job.

Outcomes by case

The same key with a different body is a different problem: the error table lists a 409 idempotency_conflict. Reuse a key only for the same operation and payload.

Submit outcomes by key and body (read 2026-10-04)
KeyBodyResult
NewAnyNew job, idempotency_hit: false
Same as an earlier submitSameOriginal job, idempotency_hit: true
Same as an earlier submitDifferent409 idempotency_conflict

Script

The script submits twice with the same key and prints request_id and idempotency_hit for each. It is a real submit, so it creates one paid job; the second call must replay it. Use a short duration.

import asyncio, json, os, sys, urllib.error, urllib.request

BODY = {"model": "seedance-2.5", "prompt": "A product clip on a desk, natural light",
        "resolution": "720p", "duration": 4, "aspect_ratio": "9:16", "mode": "async"}

def submit(key):
    req = urllib.request.Request("https://api.sume.com/v1/video-router/generate",
        data=json.dumps(BODY).encode(), method="POST",
        headers={"x-api-key": os.environ["SUME_API_KEY"], "Content-Type": "application/json",
                 "Idempotency-Key": key})
    try:
        with urllib.request.urlopen(req, timeout=60) as r:
            return json.load(r)["data"]
    except urllib.error.HTTPError as e:
        sys.exit(f"{e.code}: {e.read().decode()}")

async def main(key):
    first = await asyncio.to_thread(submit, key)
    second = await asyncio.to_thread(submit, key)
    print("first :", first["request_id"], first["idempotency_hit"])
    print("second:", second["request_id"], second["idempotency_hit"])
    assert first["request_id"] == second["request_id"], "same key must return the same job"
    assert second["idempotency_hit"] is True

asyncio.run(main(sys.argv[1]))

What to do with the flag

In production, increment a counter when the flag is true and alert if it is ever true for a key you generated fresh. That means a key-generation bug. The client-side rule for retries is in why POSTs retry only with a key.

Where keys come from

Save the key before you submit, so a crash after the response still has the key to replay. See the intent table recipe.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume