Audio detach retry: what idempotency_hit true means for the $0.01
Retrying an audio detach with the same Idempotency-Key and body returns the first job (idempotency_hit true): $0.01 once. A changed body returns 409.

If you retry an audio detach request with the same Idempotency-Key and the same body, Sume returns the original job with idempotency_hit true and you pay the $0.01 detach price once. If the body changed, the API answers 409 idempotency_conflict and creates nothing.
Three retries, three outcomes
Audio detach is $0.01 per job. Retrying is only free when the request is byte-for-byte the same payload under the same key.
| Retry | Same key | Same body | Result | New charge |
|---|---|---|---|---|
| Network timeout, resend | yes | yes | original job, idempotency_hit true | $0.00 |
| Resend with range 0-30 changed to 0-45 | yes | no | 409 idempotency_conflict | $0.00 |
| Resend with a new key | no | yes | new job | $0.01 |
Check the flag in code
The submit response carries data.idempotency_hit next to data.job.id. Log it, and skip any downstream work you already queued for that job id.
import os, requests
r = requests.post(
"https://api.sume.com/v1/audio-detach",
headers={"Authorization": f"Bearer {os.environ['SUME_API_KEY']}",
"Idempotency-Key": "ep-114-detach"},
json={"video_url": "https://media.sume.com/artifacts/artf_demo/ep.mp4",
"range": {"start": 0, "end": 30}},
)
if r.status_code == 409:
raise SystemExit("same key, different body: pick a new key on purpose")
r.raise_for_status()
d = r.json()["data"]
print(d["job"]["id"], "replay" if d["idempotency_hit"] else "new")Gotchas
Derive the key from your own stable id (episode id plus the range), not a random value, or every retry becomes a new paid job. Randomly generated keys are the usual reason a retry loop charges twice.
Detach also has caps: the source is at most 1,800 seconds and the output at most 900 seconds, so a long source needs a range. A failed job is terminal; check status before assuming the retry will return a fresh attempt.
Sources
Related posts
More in Developers
- Audio job failed: read /events and /status before you retry
A failed TTS, STT or music job is terminal. Read GET /v1/jobs/{id}/events and status, fix the cause, then resubmit under a new key. A new TTS job bills again.
- Avatar video image URLs: product_image, scene photo and backgrounds
Sume avatar video takes three kinds of image URL: product_image, scene.image_url and scene-background images. All must be public HTTPS. Checks to run first.
- File-size cap and max length: the average bitrate ceiling
Divide a platform's size cap by its longest allowed video to get an average bitrate ceiling: LinkedIn about 2.2 Mbps, TikTok 6.7, Pinterest 17.8. Python check.
- Before a 20-clip MCP burst: dry_run, admission preview, max_spend_usd
A single Sume MCP create needs none of these. A 20-job burst should use dry_run or generation_admission_preview, set max_spend_usd, and wait in one jobs_wait.
Written by Sume