Search

Search HiAPI models, tools, and resources.

Back to blog
Guide

How to Use kling-3.0/motion-control for Short-Form Video on the hiapi API

hiapi Teamklingvideo-generationhiapi-apimodel-usecase

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/ffprobe if 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:

Driver portrait used to generate the motion reference clip — a stylized city courier glancing back over one shoulder

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:

Target character for motion-control — a neon-lit street food vendor, unrelated in style and setting to the driver

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:

fieldrequirednotes
image_urlyespublic HTTPS URL, the target character
video_urlyespublic HTTPS URL, 3–30s motion reference
resolutionno (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:

resolutionprice
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_url and image_url are 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. expireAt is 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.

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.

Generate it with HiAPI

Choose a model, enter your prompt, and see the result.

HiAPI Blog

Related articles

HiAPI

Generate it with HiAPI

Start generating