Tutorial
How to Call kling-4.0/image-to-video on HiAPI: curl, Python, and Polling
What the kling-4.0/image-to-video endpoint does
kling-4.0/image-to-video turns a single still image into a short generated video. On HiAPI it is exposed as a task-based endpoint: you submit an image plus a prompt-style description of the motion you want, and the API immediately returns a task handle rather than the finished video.
At the request level, a caller supplies:
- the model identifier (
kling-4.0/image-to-video), - the source image (typically a URL to a publicly reachable image),
- a text description guiding how the image should move or what should happen in the scene,
- any extra generation options the endpoint documents for this model.
What comes back is not the video itself. The submit response carries a task identifier and an initial status. The actual clip becomes available later, once the generation job completes, and you fetch it by referring back to that identifier.
That split is the whole mental model for this endpoint: submit → receive task id → poll status → download result. Every section below is a variation on that loop.
Because the endpoint is asynchronous, your integration code has two responsibilities instead of one: it must send a well-formed submit request, and it must manage the follow-up until the task reaches a terminal state. Handle both, and the rest is plumbing.
Prerequisites and authentication
Before any call, you need two things: an HiAPI account with access to the model, and an API key issued from the dashboard.
Authentication is per-request. Every call to the API includes your key in an HTTP header — the standard pattern is a bearer-style authorization header:
Authorization: Bearer <YOUR_HIAPI_API_KEY>
Keep the key out of source control. The usual practices apply:
- Store it in an environment variable (for example
HIAPI_API_KEY) and read it at runtime. - Never paste it into client-side code or into a curl command you later commit to a repository.
- Rotate it from the dashboard if it is ever exposed.
Export it in your shell so both the curl and Python examples below can pick it up:
export HIAPI_API_KEY="your-key-here"
You will also need a reachable image URL. The endpoint consumes the image by reference, so a local file path from your laptop will not work — host the image somewhere with a stable HTTPS URL, or upload it if the platform provides an asset mechanism and use the returned reference.
Once the key is exported and the image is reachable, you are ready to submit a task.
The curl request, step by step
A complete submit request looks like this:
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": {
"image": "https://example.com/your-image.jpg",
"prompt": "A slow camera push-in as the subject turns toward the light; soft afternoon shadows"
}
}'
Reading it piece by piece:
-X POST— task creation is a POST, not a GET. The request carries a body that describes the job.- The URL — the unified HiAPI task endpoint. This is the same route for task-based models; the model is chosen in the body, not the path.
Authorization— your key, as described in the previous section.Content-Type: application/json— the body is JSON; omit or misspell this and servers commonly reject the request before it reaches the model.model— the literal stringkling-4.0/image-to-video. Treat it as exact and case-sensitive; a typo produces a model-not-found error rather than a helpful hint.input.image— the source still, as a URL. This is the frame the model animates, so choose an image that is sharp and clearly framed.input.prompt— natural-language direction. Describe motion and camera behavior (push-in, pan, subject movement) and the overall mood. Vague prompts produce vague motion.
The immediate response is a task object, not a video. It contains an identifier for the job and a status field — typically something like a queued or pending state right after submission. Store that identifier; it is the only thing you need to check on the job later.
The clip does not exist yet at this point. That is expected, and the next sections show how to handle it.
The same call in Python
The Python version does exactly the same thing — same endpoint, same headers, same body — so your choice of tooling does not change the protocol.
import os
import requests
API_KEY = os.environ["HIAPI_API_KEY"]
payload = {
"model": "kling-4.0/image-to-video",
"input": {
"image": "https://example.com/your-image.jpg",
"prompt": "A slow camera push-in as the subject turns toward the light; "
"soft afternoon shadows",
},
}
resp = requests.post(
"https://api.hiapi.ai/v1/tasks",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
},
json=payload,
timeout=60,
)
resp.raise_for_status()
task = resp.json()
task_id = task["id"]
print("submitted:", task_id, task.get("status"))
A few notes that matter in practice:
- Use
json=payloadrather than hand-serializing a string. It sets the content type for you and avoids quoting mistakes. - Keep the submit request and the follow-up in the same function or class, so the task id is never lost between steps.
- Set an explicit timeout. A submit call should return quickly; it is only creating the job, not waiting for the video.
- Do not
raise_for_status()and then continue as if the task exists — a non-2xx response means no task was created, and the error body usually tells you which field is wrong.
If you are building a service, wrap this call in a small client with one method for submitting and one for checking status. That keeps retries, logging, and key handling in a single place rather than scattered across call sites.
Polling the async task to completion
Image-to-video generation takes noticeably longer than a chat completion — the model has to synthesize many frames, encode them, and store the result. There is no way to return the clip in the submit response without holding a connection open for a long time, so the endpoint is asynchronous by design.
How to check status
Poll the task by its identifier with a GET request:
curl "https://api.hiapi.ai/v1/tasks/$TASK_ID" \
-H "Authorization: Bearer $HIAPI_API_KEY"
The response describes the job: its status plus, once finished, the output. Statuses typically move from a queued state through a running state to a terminal state — something like succeeded, or failed with an error message.
A simple polling loop in Python
import time
def wait_for_task(session, base_url, task_id, timeout=900):
deadline = time.time() + timeout
delay = 2.0
while time.time() < deadline:
r = session.get(f"{base_url}/tasks/{task_id}", timeout=30)
r.raise_for_status()
data = r.json()
status = data.get("status")
if status in ("succeeded", "failed"):
return data
time.sleep(delay)
delay = min(delay * 1.5, 15.0) # gentle backoff, capped
raise TimeoutError(f"task {task_id} did not finish in time")
Backoff guidance
- Start with short intervals and grow them. Generation will not finish in the first second, so hammering the endpoint just wastes quota and rate limit.
- Cap the interval so a finished job is noticed promptly — a few seconds is fine at steady state.
- Always set a hard deadline. A task that never completes is indistinguishable from one that takes unusually long unless you bound it.
- Poll from a background worker or scheduled job rather than blocking a web request. If a user is waiting in a UI, let the frontend poll your own status endpoint.
- Retry transient network errors on the status call, but do not retry a task that has already reported
failed— resubmitting a fresh task is the correct action there.
Retrieving and storing the returned clip
When the poll returns a terminal success, the task object contains the output reference for the generated video — an asset URL or equivalent reference pointing at the finished clip.
result = wait_for_task(session, "https://api.hiapi.ai/v1", task_id)
if result["status"] != "succeeded":
raise RuntimeError(result.get("error") or "generation failed")
video_url = result["output"]["video_url"]
with session.get(video_url, stream=True, timeout=120) as r:
r.raise_for_status()
with open(f"{task_id}.mp4", "wb") as f:
for chunk in r.iter_content(chunk_size=1 << 16):
f.write(chunk)
Two design decisions usually come up here.
Download promptly. Output URLs are often time-limited. If you intend to keep the clip, copy it to your own storage instead of handing users the raw URL and hoping it stays alive.
Store metadata alongside the file. Persist the task id, the prompt, the source image reference, and the creation time with the video. When someone later asks why a clip looks the way it does, that record is the answer.
For serving, a common pattern is: generate on the backend, store the file in object storage, and return your own stable URL to the client. Your application then controls access, expiry, and caching rather than depending on the lifecycle of a generation asset.
If you want to regenerate with different motion, submit a new task with an adjusted prompt — each task is independent, and there is no need to mutate the completed one.
Troubleshooting a failing request
When something goes wrong, work outward from the request shape. Most failures fall into a small set of causes.
The submit call returns an error
- Wrong model string.
kling-4.0/image-to-videomust match exactly, including the casing and the slash. Print the string you are actually sending rather than trusting the source file. - Authentication rejected. Confirm the header is
Authorization: Bearer <key>— not a bare key, not a query parameter — and that the environment variable is set in the same shell or process that runs the request. - Image not reachable. The image must be fetchable over HTTPS by the platform. A localhost URL, a private bucket, or a hotlink-protected host will fail. Open the URL in an incognito browser to verify it resolves without authentication.
- Missing or misnamed fields. The body nests the model inputs under
input. Puttingpromptorimageat the top level instead of insideinputis a frequent slip, and the validation error usually names the field. - Content type. Sending a JSON body without
Content-Type: application/jsongets rejected before the model is ever involved.
The task is created but never succeeds
- Poll too rarely or not at all. Check that your loop is actually running and that the status values it compares against match what the API returns. A typo in the terminal-status check produces an infinite loop.
- Task went to
failed. Read the error message in the task object; it usually points at the input — for example an image that could not be decoded. - You hit the deadline. If a job is still running near your timeout, raise the ceiling rather than resubmitting repeatedly, which only adds load and cost.
- Output link expired. If the task succeeded but the download 404s, too much time passed between completion and retrieval. Download immediately after success.
A quick diagnostic habit: log the submit response body verbatim the first few times you integrate. It tells you the task id, the initial status, and the exact field names this endpoint returns, which removes guesswork from every later failure.
Related articles
View all articles →-
Tutorial
How to Call hailuo-2.3-fast/image-to-video via the HiAPI API: curl and Python
A first working request for the fast, low-cost image-to-video tier — from model ID to downloaded MP4
9 min read -
Tutorial
How to Use hailuo-2.3/text-to-video via the HiAPI API: curl, Python, and a Working Request
11 min read -
Tutorial
Kandinsky 6.0 Pro Text-to-Video on HiAPI: curl and Python Tutorial
A request-first walkthrough: build the prompt-to-clip call, submit it, poll for completion, and download the result
11 min read