If you have ever tried to get a character in a generated video to perform a specific move — not "dancing," but this exact dance, from this exact clip — you know the usual options are bad: rotoscope it by hand, retrain a motion model, or give up and accept whatever the text prompt gives you. kling-3.0/motion-control skips all of that. You give it one character image and one reference video, and it transfers the reference's motion and expressions onto your character — no skeleton rig, no keyframing, no motion-capture suit.
This post walks through a real short-form-video workflow: animate a driver photo into a short reference clip, then use that clip to drive a completely different character image through motion-control. Every request below is a real call against hiapi's /v1/tasks API, every asset is generated during this write-up (nothing borrowed from a vendor demo gallery), and every price is pulled live from hiapi's pricing endpoint.
What kling-3.0/motion-control actually does
The model takes two inputs:
image_url— a single character portrait (the "target"). This is who ends up in the output.video_url— a motion reference clip, 3–30 seconds. This is what they do.
The output is a video of the target character performing the reference video's motion and facial expressions, rendered at 720p or 1080p. It does not take a text prompt, a duration, or an aspect ratio — the output framing simply follows the reference clip, and the length of the output tracks the length of the input clip (ours came back at 3.03s for a 3s reference).
That narrow, two-input contract is exactly what makes it useful for short-form content: you don't need a mocap rig to get a believable performance onto a brand mascot, a product character, or a stylized avatar — you just need any video of a human doing the motion you want.
What you need
- A hiapi API key (
Authorization: Bearer <key>) - A character image, publicly reachable over HTTPS (not your target — your driver)
- A second character image for the actual target you want animated
ffmpeg/ffprobeif you want to pull a cover frame or check clip duration locally, same as this post does
The real demo: one motion clip, two different characters
The point of motion-control is that the reference video and the final character don't have to match. To prove that out, this demo uses three generation steps:
1. A driver portrait, animated into a short reference clip:

That portrait was turned into a 3-second, 480p motion clip with grok-imagine-1.5/image-to-video — a quick, cheap way to manufacture a reference clip from a still image instead of sourcing one:
2. A completely different target character — same motion, different look entirely:

3. Feed both into motion-control. The vendor performs the courier's exact turn-and-glance motion:
That's the whole workflow: generate (or source) any clip of a motion you like, point motion-control at a totally unrelated character image, and get that character performing the motion. For a short-form pipeline, this means one reference library of motions can drive an unlimited number of characters without re-shooting or re-animating the reference each time.
The minimal working request (curl)
curl -s -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "kling-3.0/motion-control",
"input": {
"image_url": "https://static.hiapi.ai/blog/kling-3-0-motion-control-short-form-video/target-character.png",
"video_url": "https://static.hiapi.ai/blog/kling-3-0-motion-control-short-form-video/motion-reference.mp4",
"resolution": "720p"
}
}'
Response (task created, still processing):
{
"code": 0,
"data": {
"taskId": "tk-hiapi-01M4CWNSYA7RB637YJ2QZMHMMS",
"status": "pending"
},
"success": true
}
Poll until it's done:
curl -s https://api.hiapi.ai/v1/tasks/tk-hiapi-01M4CWNSYA7RB637YJ2QZMHMMS \
-H "Authorization: Bearer $HIAPI_API_KEY"
On our run this took about 2.3 minutes end to end (status cycled through handling roughly a dozen times before flipping to success) for a 3-second 720p clip — budget for several minutes, not seconds, when you design a polling loop. The terminal response:
{
"code": 0,
"data": {
"taskId": "tk-hiapi-01M4CWNSYA7RB637YJ2QZMHMMS",
"status": "success",
"output": [
{
"type": "video",
"url": "https://temp.hiapi.ai/7c6ttvrbpt/01M4CWNSYA7RB637YJ2QZMHMMS-0.mp4",
"storage": "temp",
"expireAt": 1792039027
}
]
},
"success": true
}
That output[0].url is a temporary link — expireAt is a Unix timestamp, and in our test it was good for roughly 24 hours. Download it and re-host it immediately; don't link to it directly from anything you publish.
The same flow in Python
import os
import time
import requests
API_BASE = "https://api.hiapi.ai/v1/tasks"
TOKEN = os.environ["HIAPI_API_KEY"]
HEADERS = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"}
def create_motion_control_task(image_url: str, video_url: str, resolution: str = "720p") -> str:
resp = requests.post(
API_BASE,
headers=HEADERS,
json={
"model": "kling-3.0/motion-control",
"input": {
"image_url": image_url,
"video_url": video_url,
"resolution": resolution,
},
},
timeout=60,
)
resp.raise_for_status()
return resp.json()["data"]["taskId"]
def wait_for_result(task_id: str, timeout_s: int = 600, poll_every: int = 10) -> str:
deadline = time.time() + timeout_s
while time.time() < deadline:
resp = requests.get(f"{API_BASE}/{task_id}", headers=HEADERS, timeout=30)
resp.raise_for_status()
task = resp.json()["data"]
if task["status"] == "success":
return task["output"][0]["url"]
if task["status"] == "fail":
raise RuntimeError(task.get("error"))
time.sleep(poll_every)
raise TimeoutError(f"task {task_id} did not finish in {timeout_s}s")
if __name__ == "__main__":
task_id = create_motion_control_task(
image_url="https://your-cdn.example.com/target-character.png",
video_url="https://your-cdn.example.com/motion-reference.mp4",
resolution="720p",
)
output_url = wait_for_result(task_id)
video_bytes = requests.get(output_url, timeout=120).content
with open("motion-control-output.mp4", "wb") as f:
f.write(video_bytes)
print(f"saved {len(video_bytes)} bytes")
Nothing exotic — create the task, poll GET /v1/tasks/{taskId} on an interval, download output[0].url the moment it's success, and re-host it yourself before the temp link expires.
Parameters motion-control actually validates
The schema is strict — anything outside these three fields gets rejected, which is useful to know up front rather than discovering it through trial and error:
| field | required | notes |
|---|---|---|
image_url | yes | public HTTPS URL, the target character |
video_url | yes | public HTTPS URL, 3–30s motion reference |
resolution | no (defaults, but worth setting) | "720p" or "1080p" — this is also what sets the price tier |
Fields you might expect from other video models — duration, aspect_ratio, prompt — are all rejected with a schema error:
{
"code": 1,
"message": "input: Object { input } has unexpected additional properties: duration, aspect_ratio",
"success": false
}
That's by design: the output's length and framing are inherited entirely from the reference video, so there's nothing for duration/aspect_ratio to control. If you want a longer or differently-framed output, trim or re-frame your reference clip before you send it, not the request.
Pricing: billed per output second, not per request
Straight from hiapi's pricing endpoint:
| resolution | price |
|---|---|
| 720p | $0.10 / output second |
| 1080p | $0.172 / output second |
Billing is driven by the actual output duration (estimated_seconds), which mirrors your reference clip's length — not a flat per-request fee. Our 3-second reference produced a 3.03-second output at 720p, landing at roughly $0.30. A 10-second reference clip at 1080p would run closer to $1.72. If you're batching this across a content calendar, the practical lever is reference-clip length: shorter reference clips cost less and still carry a full motion performance for short-form formats (Reels, TikTok, Shorts) where 3–8 seconds is often the whole clip anyway.
Production notes
- Pick your driver clip's framing deliberately. The output inherits aspect ratio, camera distance, and pacing from the reference video — a close-up reference stays a close-up output regardless of how your target character image is cropped.
- Clean expressions transfer better than subtle ones. A clear turn, wave, or head-tilt in the reference reads reliably on the target character; very small, naturalistic micro-expressions are harder for the model to pick out and transfer.
- Reuse one reference clip across many characters. Since
video_urlandimage_urlare fully independent, the highest-leverage pattern for a content pipeline is building a small library of reference motions once, then running many different character images through the same clip. - Temp output URLs are short-lived.
expireAtis typically on the order of a day — download and re-host immediately rather than queuing the download for later. - Processing isn't instant. A 3-second 720p clip took a bit over two minutes end to end in this test; plan your polling loop and any downstream automation around minutes, not the seconds a text-to-image call usually takes.
Related
Got a reference clip and a character you want to pair up? Grab a key from the pricing page and point the curl request above at your own image_url/video_url — the whole loop above is copy-pasteable.
FAQ
Can I use any video as the motion reference? Any video with a clear human (or human-like) performer works as long as it's publicly reachable over HTTPS and 3–30 seconds long. It does not need to feature your target character — that's the entire point of the model.
Does the output keep the reference video's background? No — the character's appearance, including surroundings implied by the target image, carries through; the reference video supplies motion and expression, not scene content.
Why did my request get rejected with "unexpected additional properties"?
The schema only accepts image_url, video_url, and resolution. Fields like duration, aspect_ratio, or prompt aren't part of this model's input and will 400 immediately — drop them from the payload.
How long does a motion-control task take?
Budget a few minutes per clip. Our 3-second 720p example took about 2.3 minutes from task creation to a downloadable success output.
Can I control output resolution independently of price?
No — resolution is both the output quality setting and the price-tier selector: "720p" bills at $0.10/output-second, "1080p" at $0.172/output-second.



