Home › Guides › Image to video

Animating a still with the Kling image-to-video API

Updated 2026-10-02

Image-to-video with Kling is the same POST /videos call you use for text-to-video, plus one field: start_image_url. The image becomes the first frame, and the prompt describes what should happen next. That sounds trivial, and the request is, but most wasted generations come from what you put in the prompt and the image, not from the API call. This guide covers the request, how to write motion prompts, how to pick a tier, and the mistakes worth avoiding.

The request

Base URL is https://videorouter.sh/api/v1. The job is asynchronous: you get an id back with status queued, then poll GET /videos/{id} until it reads completed or failed. Polling is free.

import time, requests

H = {"Authorization": "Bearer llmr_sk_live_...", "Content-Type": "application/json"}
BASE = "https://videorouter.sh/api/v1"

job = requests.post(f"{BASE}/videos", headers=H, json={
    "model": "kling-v3.0-std",
    "start_image_url": "https://example.com/product-shot.jpg",
    "prompt": "slow dolly-in, steam rises from the cup, soft window light flickers",
    "duration_secs": 5,
    "aspect_ratio": "16:9",
}).json()

while job["status"] not in ("completed", "failed"):
    time.sleep(5)
    job = requests.get(f"{BASE}/videos/{job['id']}", headers=H).json()

print(job["data"][0]["url"] if job["status"] == "completed" else job["error"])

start_image_url accepts either a public https:// URL or an inline data:image/...;base64,... URI. If the file only exists locally and base64 would make the request unwieldy, the API reference describes a POST /v1/uploads endpoint that stages a file and returns a URL you can pass back in.

Fields that matter for image-to-video

FieldNotes
modelOne of kling-v3.0-std, kling-v3.0-pro, kling-v3.0-4k. Tiers are separate model ids, not a quality flag.
start_image_urlThe first frame. Public URL or data URI.
promptMotion and camera description. Do not re-describe the image.
duration_secsBilled once at creation by the duration you request, so do not ask for more than you will use.
aspect_ratio, resolutionOptional. The API reference says an unsupported combination is silently ignored and the model default is used rather than returning a 400, so check the output dimensions, not just the status code.

Two constraints from the API reference are easy to trip over. First, a model accepts either a start image or a set of reference files in one call, not both (the one documented exception is Motion Control, covered in a separate guide). Second, end_image_url is currently documented as rejected with a 400 on every model, so do not build a first-frame-plus-last-frame flow on it without checking the model page first.

Writing a motion prompt

The image already carries the subject, composition, lighting and style. The prompt only needs to add change over time. A structure that works well in practice:

  1. Camera move: static, slow push-in, pan left, orbit. One move per clip.
  2. Subject motion: what moves, and how fast. "Hair lifts slightly in a light breeze" beats "dynamic motion".
  3. Environment motion: background elements that should stay alive (steam, leaves, traffic).
  4. What must not change: logos, text on packaging, faces. Say it plainly.

Avoid re-describing what is visible ("a woman in a red coat standing on a street") because it adds nothing and can pull the model toward a different interpretation than your frame. Avoid stacking three camera moves in five seconds; the result tends to look like a mistake rather than a style.

Choosing a tier

Image-to-video is the case where tiering pays off most, because the image fixes the look and you are mostly testing motion. A sensible loop is to iterate prompts on kling-v3.0-std, then resubmit the approved prompt and image on kling-v3.0-pro or kling-v3.0-4k for the deliverable. The swap is one string. See the Std vs Pro vs 4K guide for the workflow and the live price table for current per-second rates.

If you want a particular host, suffix the model id, for example kling-v3.0-std/novita. Without a suffix, requests route to the cheaper healthy host. Pin only when you have a reason, such as keeping output consistent across a batch.

Pitfalls

Handling errors

Errors use the OpenAI-style envelope {"error": {"message", "type", "code"} }. A bad image URL or a missing required field comes back as a 400 invalid_request_error at creation time; a failure after queuing shows up as status: "failed" on the job. Handle both paths.

For a runnable first request with your own key, see the quickstart, or create an account and try the snippet above.

Frequently asked questions

Which field animates a still image with Kling?

Pass start_image_url alongside prompt on POST /videos. It takes a public https URL or a base64 data URI, and the image becomes the first frame.

Can I send a last frame as well?

The API reference currently states end_image_url is rejected with a 400 on every model. Check the model page before designing around first-and-last-frame control.

Do I need a different model id for image-to-video?

No. The same Kling model ids (for example kling-v3.0-std) handle text-to-video and image-to-video; adding start_image_url switches the mode.

Am I billed if the job fails?

Jobs that fail upstream are not billed. Billing happens once at creation by the duration you requested, and polling the job is free.

Keep reading

Using Kling is one part of the job.

VideoRouter puts it next to dozens of other video and image models behind one API key, so you can compare providers, prices and fail over automatically. Compare providers on VideoRouter →