
Kling 3.0 Turbo animates a single still image into a short video clip from a text prompt. This guide shows you how to call kling-3.0-turbo/image-to-video through hiapi's unified task API — the exact request shape, a copy-paste curl and Python example, async polling, callbacks for production, and the errors you'll actually hit.
Goal: send one image (the first frame) plus a motion prompt, get back a video URL.
Prerequisites:
sk-... and go in the Authorization: Bearer header.localhost links, signed URLs that have expired, or files behind auth will make the task fail after creation (more on that below).Video generation is asynchronous: you POST /v1/tasks to create a task, then either poll GET /v1/tasks/<taskId> or receive a callback when it finishes.
kling-3.0-turbo/image-to-video accepts exactly four input fields — the schema is strict and rejects anything else:
| Field | Type | Required | Notes |
|---|---|---|---|
prompt | string | ✅ | Describe the motion you want, not just the scene |
image_urls | array of strings | ✅ | Exactly 1 URL — the first frame. Public URLs only |
duration | integer | optional | Seconds, 3–15 |
resolution | string | optional | "720p" or "1080p" |
Two things that trip people up:
duration is an integer, not a string. Sending "5" returns 400 invalid input: duration: got string, want integer. (Some other video models on the platform take duration as a string — this one doesn't.)aspect_ratio, mode, negative_prompt, seed, or cfg_scale here — passing any of them fails validation with additional properties ... not allowed. Output framing follows your input image.curl -X POST "https://api.hiapi.ai/v1/tasks" \
-H "Authorization: Bearer sk-<your-key>" \
-H "Content-Type: application/json" \
-d '{
"model": "kling-3.0-turbo/image-to-video",
"input": {
"prompt": "The cat slowly turns its head toward the camera, tail swaying, soft window light",
"image_urls": ["https://your-cdn.example.com/first-frame.jpg"],
"duration": 5,
"resolution": "1080p"
}
}'
A successful creation returns immediately with a task ID:
{
"code": 200,
"data": { "taskId": "tk-hiapi-01KX65CVB7ENFZVJGE4SZDAT1X" },
"message": "success"
}
Then poll:
curl "https://api.hiapi.ai/v1/tasks/tk-hiapi-01KX65CVB7ENFZVJGE4SZDAT1X" \
-H "Authorization: Bearer sk-<your-key>"
While the task is running you'll see a non-terminal status; when it finishes, data.status is "success" and the video URL is at data.output[0].url.
No SDK needed — requests is enough:
import os
import time
import requests
API_BASE = "https://api.hiapi.ai/v1"
HEADERS = {
"Authorization": f"Bearer {os.environ['HIAPI_API_KEY']}",
"Content-Type": "application/json",
}
def create_task(prompt: str, image_url: str, duration: int = 5,
resolution: str = "1080p") -> str:
resp = requests.post(f"{API_BASE}/tasks", headers=HEADERS, json={
"model": "kling-3.0-turbo/image-to-video",
"input": {
"prompt": prompt,
"image_urls": [image_url], # exactly one URL
"duration": duration, # integer, 3-15
"resolution": resolution, # "720p" | "1080p"
},
}, timeout=30)
resp.raise_for_status()
return resp.json()["data"]["taskId"]
def wait_task(task_id: str, poll_every: int = 10, timeout: int = 900) -> dict:
deadline = time.time() + timeout
while time.time() < deadline:
resp = requests.get(f"{API_BASE}/tasks/{task_id}",
headers=HEADERS, timeout=30)
resp.raise_for_status()
data = resp.json()["data"]
if data["status"] == "success":
return data
if data["status"] == "fail":
raise RuntimeError(f"task failed: {data.get('error')}")
time.sleep(poll_every)
raise TimeoutError(f"task {task_id} did not finish in {timeout}s")
task_id = create_task(
prompt="The cat slowly turns its head toward the camera, tail swaying",
image_url="https://your-cdn.example.com/first-frame.jpg",
)
result = wait_task(task_id)
video_url = result["output"][0]["url"]
# Output URLs are temporary (they carry an expiry) - download immediately:
video_bytes = requests.get(video_url, timeout=120).content
with open("kling-turbo-output.mp4", "wb") as f:
f.write(video_bytes)
print(f"saved {len(video_bytes)} bytes")
Download the output right away. Task storage is temporary and output URLs expire — persist the bytes to your own storage (S3, R2, local disk) as soon as the task succeeds. Don't hotlink the task URL in your app.
For anything beyond a script, register a callback at task creation and skip polling entirely. The callback object sits at the top level of the request, next to model and input:
{
"model": "kling-3.0-turbo/image-to-video",
"input": {
"prompt": "...",
"image_urls": ["https://your-cdn.example.com/first-frame.jpg"],
"duration": 5
},
"callback": {
"url": "https://yourapp.example.com/hooks/hiapi",
"when": "final"
}
}
With "when": "final" your endpoint gets hit once, when the task reaches a terminal state. Rule of thumb: polling is fine for CLIs, notebooks, and batch jobs where the process stays alive; callbacks are better for web backends where you don't want a worker parked on a 1–3 minute video render.
If your job runner might retry a crashed worker, guard task creation — otherwise a retry renders (and bills) the same clip twice. Store the taskId keyed by your own job ID before you consider the work "started", and on retry check for an existing taskId and resume polling instead of re-creating.
Errors you'll actually encounter, and what they look like:
"code": "permission_denied" and a request_id. The message is "This API key cannot use the selected model." — check the key and its model permissions in your dashboard, and include the request_id if you contact support.INVALID_REQUEST with a precise message, e.g. duration: maximum: got 20, want 15 or image_urls: maxItems: got 3, want 1. These fail before the task is created, so they cost nothing.200 with a taskId — and then the task itself lands in "status": "fail" with TASK_FAILED. If your tasks fail consistently, verify the image URL opens in an incognito browser tab first.Billing is per generated video and scales with duration and resolution. Current rates for both the 720p and 1080p tiers are on the pricing page, and you can try the model interactively on the kling-3.0-turbo/image-to-video model page before wiring it into code.
Can I pass multiple reference images to kling-3.0-turbo/image-to-video?
No. image_urls accepts exactly one URL — the first frame. Sending more returns 400 image_urls: maxItems: got N, want 1. If you need multi-reference conditioning, look at the Omni tier's reference-to-video endpoint instead.
What durations does the kling 3.0 turbo image to video API support?
Any integer from 3 to 15 seconds. Values outside that range are rejected at validation time (minimum: got 0, want 3 / maximum: got 20, want 15), so out-of-range requests never create a task or incur cost.
Does it support aspect ratio or seed parameters?
No. The input schema is strict: prompt, image_urls, duration, and resolution only. The output framing follows your input image, so crop the first frame to the aspect you want before submitting.
Why did my task fail even though the request returned 200?
Almost always an unreachable image_urls entry. Type validation passes at creation, then the render fails when the backend can't fetch your image. Use a public, non-expiring URL.
How long does a render take? Typically on the order of a minute or two depending on duration and resolution — plan for asynchronous handling (polling or callbacks) rather than a blocking HTTP request.
Is there a synchronous endpoint for video?
No — video generation goes through POST /v1/tasks. The task API is the single integration surface for all async models on hiapi, so the polling/callback code above transfers unchanged to other video models.