HiAPI
Blog

Tutorial

Kling 3.0 Turbo Image-to-Video API in Python: Schema, Full Script and Errors

A version-specific reference for Turbo's single-image schema, 3-15 second limits, 720p/1080p tiers, and failure modes.

Jul 11, 2026
Kling 3.0 Turbo Image-to-Video API in Python: Schema, Full Script and Errors

kling-3.0-turbo/image-to-video has a small, strict schema: one public image URL, a required motion prompt, an integer duration from 3 to 15 seconds, and either 720p or 1080p output. This page is the version-specific reference for validating that payload and separating HTTP 400 errors from asynchronous TASK_FAILED results.

For the shared create, polling, callback, and download workflow, use the Kling Python task runner and Omni/Turbo chooser.

Turbo input schema

Field Type Required Accepted value
prompt string Yes Describe motion and camera behavior
image_urls array of strings Yes Exactly one publicly reachable image URL
duration integer No 3 through 15 seconds
resolution string No 720p or 1080p

Turbo does not accept sound, aspect_ratio, negative_prompt, seed, mode, or a second image URL. A strict schema rejects extra properties instead of silently ignoring them.

The input image determines the frame shape. Upload it to a public bucket or CDN before creating the task; localhost, private dashboards, and expired signed URLs cannot be fetched by the renderer.

Minimal valid request

curl -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "kling-3.0-turbo/image-to-video",
    "input": {
      "prompt": "The runner accelerates as the camera tracks from the side",
      "image_urls": ["https://cdn.example.com/first-frame.jpg"],
      "duration": 6,
      "resolution": "1080p"
    }
  }'

A successful response contains data.taskId. HTTP 200 confirms creation, not completion; the video render continues asynchronously.

Python preflight validation

Validate the constraints before the network call so obvious mistakes never create a task:

from urllib.parse import urlparse

import requests


def turbo_input(prompt: str, image_url: str, duration: int, resolution: str) -> dict:
    if not prompt.strip():
        raise ValueError("prompt is required")
    if type(duration) is not int or not 3 <= duration <= 15:
        raise ValueError("duration must be an integer from 3 to 15")
    if resolution not in {"720p", "1080p"}:
        raise ValueError("resolution must be 720p or 1080p")
    parsed = urlparse(image_url)
    if parsed.scheme not in {"http", "https"} or not parsed.netloc:
        raise ValueError("image_url must be a public HTTP(S) URL")

    return {
        "prompt": prompt,
        "image_urls": [image_url],
        "duration": duration,
        "resolution": resolution,
    }


payload = {
    "model": "kling-3.0-turbo/image-to-video",
    "input": turbo_input(
        prompt="The runner accelerates as the camera tracks from the side",
        image_url="https://cdn.example.com/first-frame.jpg",
        duration=6,
        resolution="1080p",
    ),
}

response = requests.post(
    "https://api.hiapi.ai/v1/tasks",
    headers={"Authorization": "Bearer sk-your-key"},
    json=payload,
    timeout=30,
)
response.raise_for_status()
task_id = response.json()["data"]["taskId"]
print(task_id)

This local check catches value shape, but it cannot prove that the remote image is publicly downloadable. The renderer performs that fetch after creation.

Full Python script: submit, poll, download

The snippet above stops at the task id. This version runs the whole job and saves the MP4. It sends an Idempotency-Key header, so retrying the same submission after a timeout returns the original task instead of creating and billing a second one:

import os
import time
import uuid

import requests

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


def run_turbo(prompt: str, image_url: str, duration: int = 5,
              resolution: str = "720p", out_path: str = "turbo.mp4") -> str:
    body = {
        "model": "kling-3.0-turbo/image-to-video",
        "input": turbo_input(prompt, image_url, duration, resolution),
    }
    key = str(uuid.uuid4())  # reuse this key if you retry the same submission
    r = requests.post(API, json=body, headers={**HEADERS, "Idempotency-Key": key}, timeout=30)
    if r.status_code == 400:
        raise ValueError(f"schema error, no task created: {r.text}")
    r.raise_for_status()
    task_id = r.json()["data"]["taskId"]

    deadline = time.time() + 900
    while time.time() < deadline:
        data = requests.get(f"{API}/{task_id}", headers=HEADERS, timeout=30).json()["data"]
        if data["status"] == "success":
            video = requests.get(data["output"][0]["url"], timeout=120)
            video.raise_for_status()
            with open(out_path, "wb") as f:
                f.write(video.content)  # the output URL is temporary, so save it now
            return out_path
        if data["status"] == "fail":
            raise RuntimeError(f"TASK_FAILED {task_id}: {data.get('error')}")
        time.sleep(10)
    raise TimeoutError(task_id)


print(run_turbo(
    "The runner accelerates as the camera tracks from the side",
    "https://cdn.example.com/first-frame.jpg",
))

A 5-second 720p clip like this one costs $0.65. In production, replace the polling loop with a top-level "callback": {"url": "https://your-app.example.com/hiapi/callback"} and deduplicate notifications by task id.

HTTP 400: the task was never created

An HTTP 400 response is synchronous schema validation. Typical causes are:

Invalid input Why it fails Fix
"duration": "5" A string was sent instead of an integer Send 5
"duration": 20 The maximum is 15 Choose 3-15
Two image_urls Turbo accepts exactly one Keep only the starting frame
"resolution": "4K" Turbo supports 720p and 1080p Use 720p or 1080p, or choose Omni
"sound": true sound is not in the Turbo schema Remove it or choose Omni
Missing prompt Turbo requires a prompt Add a non-empty motion prompt

Because validation failed before creation, there is no task id to poll. Log the response body and request id, correct the payload, and submit a new request.

TASK_FAILED: creation succeeded, rendering did not

If POST /v1/tasks returned HTTP 200 and a task id, later failure belongs to the asynchronous task lifecycle. Treat it separately from a 400.

Common causes include:

  • the image URL requires cookies or an authorization header;
  • a signed image URL expired before the renderer fetched it;
  • the host blocks server-side fetching or redirects to an HTML page;
  • the file is corrupt or its media type does not match its contents;
  • the provider could not complete that render.

Record the task id, terminal error code and message, original input URL, and request time. Before retrying, verify that an unauthenticated server can fetch the image with a 2xx response and an image content type. Blindly retrying the same inaccessible URL only creates another failed task.

The generic Python guide contains the shared terminal-status loop and output download code; keeping it there avoids duplicating lifecycle logic on this version reference.

Turbo pricing and model choice

Current production rates are:

Turbo tier Price per second 5-second task
720p $0.130 $0.65
1080p $0.160 $0.80

Check the live pricing page before a large batch. Turbo's name describes the model variant; it is not a promise that every resolution is cheaper or that a task will meet a fixed render-time SLA.

Choose standard Kling 3.0 (kling-3.0/image-to-video) when you need a last frame, audio, or 4K: it takes one or two images (start and end frame), has a sound switch, and costs $0.10/s at 720p or $0.129/s at 1080p without sound. Choose Omni when you need reference images or reference video. For a single input frame at 720p or 1080p, benchmark the variants with your own motion prompts and judge output quality, queue time, and total cost.

FAQ

How many images does Kling 3.0 Turbo image-to-video accept?

Exactly one public image URL. A second image is a schema error (image_urls: maxItems: got 2, want 1); use standard Kling 3.0 image-to-video for first-and-last-frame control.

What duration values are valid?

Send an integer from 3 through 15. A quoted number such as "5" is a string and fails validation.

Does Turbo support 4K or sound?

No 4K: this endpoint accepts 720p or 1080p. There's no sound field, but Turbo clips come with sound already, and you can't turn it off. Use standard Kling 3.0 or Omni when you need 4K or silent output.

What is the difference between HTTP 400 and TASK_FAILED?

HTTP 400 means no task was created because the request violated the schema. TASK_FAILED occurs after a valid create response, during input fetching or generation.

Why does a public-looking image URL cause TASK_FAILED?

The URL may depend on cookies, have an expired signature, block server fetches, or return HTML instead of image bytes. Test it without browser authentication before retrying.

Related articles

View all articles →

Start building with persistent AI artifacts

Get your API key and call image, video and audio models through one task-based API.

Read the API docs