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.

5 min readSume
All posts

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

Submit outcomes for a motion control job (read 2026-10-05)
OutcomeMeaningAction
202 with a jobAdmitted and reservedStore job.id, poll the status
Same key, same bodyReplay of the first jobUse the returned job
409 conflictSame key, different bodyMake a new key for the new body
402Balance below the reserveAdd balance, or declare a shorter clip
429 queue_fullNo accepted-job capacity leftWait, 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

All Developers posts

Written by Sume