Search

Search HiAPI models, tools, and resources.

Back to blog
Tutorial

Kling 4.0 Image-to-Video API: Request Reference and a Real Run

hiapiklingimage-to-videoapitutorial

Kling 4.0 Image-to-Video animates one first-frame image into a 3–30 second clip with synchronized audio. This guide gives you the exact request that works on hiapi, the fields the API accepts and rejects, and what a real run looked like. You need an API key from the hiapi dashboard; the model id is kling-4.0/image-to-video, and the model page lists the current spec.

If you are still on Kling 3.0, the Kling 4.0 API overview was written before launch and describes it as announced; this page is the working reference now.

Minimal request (curl)

curl -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "kling-4.0/image-to-video",
    "input": {
      "prompt": "She turns her head slightly toward the camera, hair moving in the wind, camera slowly pushes in, ambient wave sound",
      "image_urls": ["https://example.com/first-frame.jpg"],
      "duration": 3,
      "resolution": "720p"
    }
  }'

The response returns a task id immediately:

{"code":200,"data":{"taskId":"tk-hiapi-..."},"message":"success"}

Poll GET /v1/tasks/<taskId> until data.status is success, then read data.output[0].url.

Python: submit and poll

import os, time, requests

BASE = "https://api.hiapi.ai"
HEADERS = {"Authorization": f"Bearer {os.environ['HIAPI_API_KEY']}"}

def kling4_i2v(image_url, prompt, duration=5, resolution="720p"):
    r = requests.post(f"{BASE}/v1/tasks", headers=HEADERS, json={
        "model": "kling-4.0/image-to-video",
        "input": {
            "prompt": prompt,
            "image_urls": [image_url],   # exactly one first frame
            "duration": duration,        # integer, 3-30
            "resolution": resolution,    # "720p" or "1080p"
        },
    })
    body = r.json()
    if body["code"] != 200:
        raise RuntimeError(body["message"])
    task_id = body["data"]["taskId"]

    while True:
        d = requests.get(f"{BASE}/v1/tasks/{task_id}", headers=HEADERS).json()["data"]
        if d["status"] == "success":
            return d["output"][0]["url"]
        if d["status"] == "fail":
            raise RuntimeError(d)
        time.sleep(10)

Download the returned MP4 straight away. The output URL carries an expireAt timestamp (about seven days out in our run) and is hosted on a temporary domain, so copy it into your own storage rather than hot-linking it.

Fields the API accepts

We probed the endpoint with deliberately invalid values (these return HTTP 400 INVALID_REQUEST and do not create a task):

FieldRule observed
image_urlsRequired array, exactly one item (two items → maxItems: got 2, want 1)
durationInteger, minimum 3, maximum 30. A string like "5" is rejected (got string, want integer)
resolution720p or 1080p
aspect_ratioOptional: 16:9, 9:16, 1:1, 21:9. The model page says framing follows the source image when omitted
promptMotion description; we sent requests without one in the validator probe only, so treat it as recommended

Two things that trip people up when porting code from other video models:

  • The field is image_urls, not image_url. Sending image_url fails with both "missing required field image_urls" and "additional properties image_url not allowed".
  • There is no audio switch. audio, generate_audio and sound are each rejected as additional properties. Audio is always generated with the picture; steer it in the prompt (the example above asks for ambient wave sound).

Unknown fields are rejected rather than ignored, so a typo surfaces as a 400 instead of a silently ignored setting.

Production notes

  • Prefer a callback over polling. Clips take minutes, so pass callback with url and when: "final" on task creation and handle the result in your webhook; keep polling only as a fallback.
  • Validate before you pay. Because invalid input returns 400 with no task created, check duration and resolution in your own code first; a bad request costs nothing but a good one bills for every second you ask for.
  • Billing is per second and depends on resolution. Longer clips and 1080p cost more; see pricing for the current rates rather than relying on a number in an article.
  • Save the task id. If a poll fails on a transient network error, re-poll the same id instead of resubmitting, which would create and bill a second clip.

Tested run

We submitted the request above: one portrait first frame, duration: 3, resolution: "720p", no aspect_ratio.

  • Status sequence seen while polling every ~10 s: handling → archiving → success.
  • Total wall time: about 274 seconds for a 3-second clip. handling accounted for roughly 255 s; plan for minutes, not seconds, and design around a callback.
  • Output file (checked with ffprobe): H.264 video at 1280×720, 3.04 s, plus an AAC audio track of the same length. So the audio is real, embedded in the MP4, and the framing followed the 16:9 source.

What we did not test: 1080p, durations above 3 seconds, and non-default aspect_ratio values. Those limits come from the API validation messages and the model page, not from generated output.

FAQ

Can I turn off audio on Kling 4.0 image-to-video?

Not through a request field. audio, generate_audio and sound were all rejected by the validator. Describe the sound you want (or none) in the prompt.

How many input images can I send?

One. image_urls must contain exactly one first-frame image.

How long does a request take?

Our 3-second 720p clip took about 274 seconds end to end. Use a callback for production workloads.

What is the longest clip?

The API accepts duration up to 30 (integer seconds) and rejects higher values; we only generated a 3-second clip ourselves.

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