Retry a Kling motion control submit without paying twice
All four Sume motion-control routes take an Idempotency-Key. Resend the same body and key after a timeout and you get the original job, not a second reserve.

Send an Idempotency-Key header on every Sume Kling motion control submit. All four submit paths accept it. If your client times out and you resend the same body with the same key, you get the job that already exists; a different body under the same key is a 409, not a new charge.
What can go wrong on a 30-second job
A motion job reserves ceil(duration_seconds) times $0.1575 at admit. For a 30-second reference that is $4.725. If a timeout hides the 202 and you submit again without a key, you can end up with two jobs and two reserves. The key is what turns the second call into a replay.
The pattern
Make the key from your own business id (the shot, not a random value), so that a restarted worker derives the same key. Keep it under 255 printable characters. Use mode: "async" and poll, because a render of that length will not finish inside a sync wait.
import os, requests
body = {
"image_url": "https://example.com/character.png",
"motion_video_url": "https://example.com/walk.mp4",
"duration_seconds": 12,
"mode": "async",
}
url = "https://api.sume.com/v1/kling/3.0/motion-control"
h = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}",
"Idempotency-Key": "shot-0042-motion-v1"}
for attempt in range(3):
try:
r = requests.post(url, headers=h, json=body, timeout=30)
break
except requests.Timeout:
continue
else:
raise SystemExit("submit timed out three times")
print(r.status_code, r.json()["job"]["id"])Which status needs which action
| Outcome | Meaning | Action |
|---|---|---|
| 202 with a job | Admitted and reserved | Store job.id, poll the status |
| Same key, same body | Replay of the first job | Use the returned job |
| 409 conflict | Same key, different body | Make a new key for the new body |
| 402 | Balance below the reserve | Add balance, or declare a shorter clip |
| 429 queue_full | No accepted-job capacity left | Wait, then retry with the same key |
A note on 429
Sume accepts more valid jobs than it runs at once, so a full concurrency limit alone is not an error; the job waits as queued. Only a full queue returns 429 queue_full, and Sume's own guidance is to retry with the same idempotency key. Keep that key stable across the retry loop.
What to store
Write the key, the body hash and the returned job.id to your own database before you poll. If your worker restarts, it reads the row, skips the submit and resumes the poll. That is what makes the key useful: it is a record in your system too, not only a header.
Do not reuse a key for a new take of the same shot. A new take is a new body (a different motion window, a new still), so give it a new key, such as shot-0042-motion-v2.
A failure that is not a retry
If the job itself fails, a new submit with the same key replays the failed job, so use a new key for a deliberate re-run. Sume reserves at admit, captures on completion, and refunds on failure, so a failed motion job does not keep the $4.725 that you reserved. The new job reserves again under its own key.
Check the failure text before you resubmit. A message about an input media URL means the still or the motion video was not reachable, and a second submit with the same URLs fails the same way.
Sources
Related posts
More in Developers
- Retry a video-trim: same key for the same body, new key for new start
A trim that timed out can be retried with the same Idempotency-Key and body. Changing start, duration or precision is a new operation and needs a new key.
- Rolling a webhook secret: Stripe's 24-hour overlap vs Sume's header
Stripe can keep an old signing secret live for up to 24 hours. Sume sends one sume-v1 entry per live secret; verify any match. Python verifier included.
- Route a Kling video job in Python: scene, motion clip or 30 seconds
A small Python router for Sume: performance copies go to Kling motion control, 4-15 s scenes to kling-3, and longer jobs to a catalog id that lists 30 s.
- Route low-confidence transcripts to review: STT language_probability
Sume STT returns language_code and language_probability. Flag results under a threshold you set and send them to a person. Python, about 10 cents per file.
Written by Sume