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.

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.
| Key | Body | Result |
|---|---|---|
| New | Any | New job, idempotency_hit: false |
| Same as an earlier submit | Same | Original job, idempotency_hit: true |
| Same as an earlier submit | Different | 409 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
- Sume submit budgets: 120 to 1,200 writes a minute for an Omni batch
Sume rate limits submits per plan: Free 120, Pro 300, Startup 600 and Scale 1,200 a minute; reads get 40 times that. Why queue size, not the rate, paces Omni.
- Sume sync mode waits at most 30 seconds: short TTS vs long scripts
mode sync is a bounded wait, clamped to 0-30 seconds, not a promise the audio is ready. When to use it, and when to go async or webhook.
- Sume TTS 1.0 rejects model and model_id: use the router to pick
TTS 1.0 has no engine picker and returns 400 for model or model_id. The TTS router takes a required model from its catalog. Compare with ElevenLabs model tiers.
- Sume TTS emotion is a 64 character string: write a guide that fits
The emotion field in Sume TTS generation_config takes 1 to 64 characters. How to write a short, usable guide and test it against a neutral take.
Written by Sume