Kling motion control: send image_url or avatar_handle, not both
Sume's Kling 3.0 Motion Control takes exactly one visual source: image_url or an avatar_id/avatar_handle. A short Python check before the $0.1575/s submit.
Kling 3.0 Motion Control on Sume needs one visual source for the character: either image_url, or an avatar_id or avatar_handle for a ready avatar. Sending both, or neither, is refused. The driving video goes in motion_video_url and must be a public HTTPS URL.
The rule
The schema shares its visual-source contract with VEED Fabric 1.0 and MiniMax H3 Max Lip Sync: image_url XOR avatar_id/avatar_handle. That is useful, because a client that already builds a Fabric request only needs to swap audio_url for motion_video_url.
| You send | Result |
|---|---|
| image_url only | Accepted |
| avatar_handle only | Accepted |
| avatar_id only | Accepted |
| image_url and avatar_handle | Refused |
| Neither | Refused |
| reference_image_urls or reference_video_urls | Refused, the body is strict |
A request builder
This Python builds the JSON for the call and refuses ambiguous input. It uses only the standard library and stops if SUME_API_KEY is missing.
import json, os, urllib.request
def build(driver_url, seconds, image_url=None, avatar_handle=None):
if bool(image_url) == bool(avatar_handle):
raise ValueError("send exactly one of image_url or avatar_handle")
if not 1 <= seconds <= 30:
raise ValueError("duration_seconds must be 1 to 30")
body = {"motion_video_url": driver_url, "duration_seconds": seconds}
body["image_url" if image_url else "avatar_handle"] = image_url or avatar_handle
return body
key = os.environ["SUME_API_KEY"]
body = build("https://media.sume.com/driver.mp4", 12,
image_url="https://media.sume.com/still.png")
req = urllib.request.Request(
"https://api.sume.com/v1/kling/3.0/motion-control",
data=json.dumps(body).encode(),
headers={"Authorization": f"Bearer {key}",
"Content-Type": "application/json",
"Idempotency-Key": "motion-demo-001"})
print(urllib.request.urlopen(req).read().decode())After the submit
The call returns a job. Poll GET /v1/jobs/{id}/status and then /result, or use a webhook. A 12-second driver reserves 12 x $0.1575 = $1.89. Keep the same Idempotency-Key when you retry after a network error, so a second submit does not create a second charge.
Why the XOR rule exists
A single request must say who the character is exactly once. If both were allowed, the server would have to guess which wins, and a wrong guess would cost $0.1575 for every second of the driver. Failing at validation is free, so the rule protects your balance.
The same rule applies on VEED Fabric and H3 Max Lip Sync, so one request-building function can serve all three, with the extra field varying: audio_url and duration for the talking routes, and motion_video_url plus duration_seconds here. Note that the Fabric and lip-sync bodies also take a resolution, while Kling motion control has no resolution setting.
Cost sanity check for the sample above: a 12-second driver is 12 x $0.1575 = $1.89, and the catalog's estimate for the route is 79 cents, which is a default-length estimate and not your price.
Sources
Related posts
More in Developers
- Kotlin: submit and poll a Sume video job with java.net.http
A Kotlin port of a Sora videos call: POST /v1/videos with an Idempotency-Key, poll every 30 seconds until done, with the JDK client and kotlinx.serialization.
- Kubernetes CronJob that submits a nightly 30-second Wan 3.0 clip
A CronJob manifest using curlimages/curl and a Secret: one dated Idempotency-Key per night, concurrency forbidden, and a month of reserves at each resolution.
- AWS Lambda's 3 s default vs POST /v1/images' 30 s sync wait
POST /v1/images waits up to 30 seconds by default, but a Lambda defaults to 3. Send mode async with an Idempotency-Key and return the job id instead.
- Lambda get_remaining_time_in_millis: stop polling a Sume job in time
A Lambda that polls a Sume job should check get_remaining_time_in_millis before each sleep and return the job id so a later invocation can resume. Python.
Written by Sume