Tutorial
How to Call the Kling 2.5 Turbo Text-to-Video API: curl and Python Guide
What the kling-2.5-turbo/text-to-video endpoint gives you
kling-2.5-turbo/text-to-video is a text-to-video model exposed on HiAPI. You give it a text prompt and it returns a generated video. The "turbo" in the name tells you what it optimizes for: faster turnaround than the standard quality tier, at the cost of some fine detail. For draft renders, storyboard previews, social cutdowns and creative iteration, that trade is usually the right one.
The single most important thing to internalize before writing a line of code is that this is an asynchronous endpoint. Video generation takes seconds to minutes, far longer than any sane HTTP timeout. So the API is split into two operations:
- Submit — you POST your prompt and parameters, and get back a job identifier immediately.
- Poll — you repeatedly GET the job by that identifier until it reaches a terminal state, then read the video result from the completed payload.
If you try to model this as one blocking call, you will hit timeouts, duplicate generations on retry, and connection resets under load. The submit-then-poll pattern is not an inconvenience to work around; it is the correct mental model. Every code sample below is built around it, and once you internalize it, the rest of the integration is straightforward.
One more expectation to set: the endpoint is stateful on the server side, not on yours. The job exists independently of your HTTP connection. Your script can crash mid-poll, restart, and pick the same job back up by ID — as long as you persisted that ID somewhere. Design for that from the start.
Request anatomy: prompt, fast preset, and flexible duration
You only control a handful of fields, but each one changes the job meaningfully. Here is what actually matters.
The prompt
The prompt is the primary creative input. Treat it like a shot description rather than a search query. Good prompts for this model tend to include:
- Subject — who or what is on screen.
- Action — what the subject is doing across the clip.
- Camera — framing and movement, e.g.
slow dolly in,static wide shot,handheld follow. - Environment and light — setting, time of day, mood.
- Style —
cinematic,anime,product photographyand so on, if you want to steer the look.
Keep it concrete and front-load the subject. Vague prompts produce vague motion, and the turbo tier amplifies that because it has less room for interpretation.
The fast/turbo preset
The model family supports a fast preset that trades some refinement for latency. When you enable it, you are telling the backend to prioritize speed — motion is still coherent, but fine texture, small faces and busy backgrounds degrade sooner than in the quality path. Use it for iteration loops where you are testing prompt variants, and when you eventually render a hero take, consider whether the quality path is the better final step. The preset is a request field, so it is a per-job decision, not a global account setting.
Flexible duration
Duration is flexible within the model's supported range rather than fixed to a single length. Longer durations mean more frames to generate, so they take longer to complete and cost more — billing is by output seconds. Pick the shortest duration that serves the shot. A five-second loop for a social post does not need the same length as a narrative beat.
Field discipline
Set only the fields you need. Unknown or misspelled keys are a common source of silent misconfiguration, so keep the JSON body small, explicit, and version-controlled alongside the rest of your pipeline config.
Making the request with curl
Every HiAPI call uses the same auth pattern: a bearer token in the Authorization header. Keep the key in an environment variable so it never lands in shell history or a committed script.
export HIAPI_API_KEY="your_api_key_here"
Then submit the job. The exact field names and the endpoint path should be taken from the model reference; the shape below is the canonical one:
curl -X POST "https://api.hiapi.ai/v1/video/generations" \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "kling-2.5-turbo/text-to-video",
"prompt": "A slow dolly-in on a ceramic coffee cup on a wooden table, morning light raking across the surface, steam rising, shallow depth of field, cinematic",
"duration": 5,
"fast": true
}'
What you should expect back is not a video. It is an acknowledgment containing the job identifier and its initial state — typically a status of queued or processing, plus an id field. Capture that ID; it is the handle for everything that follows.
A minimal response looks roughly like this:
{
"id": "job_abc123",
"status": "queued",
"model": "kling-2.5-turbo/text-to-video"
}
The submit call should return quickly — well under a second in normal conditions. If it hangs, the problem is connectivity or auth, not the model. If it returns 401, check the header format (Bearer prefix matters). If it returns 422, your JSON body is malformed or a required field is missing.
Do not add -L, retries, or long timeouts to this call. The submit step either succeeds fast or fails fast. All the waiting happens in the poll loop, which is next.
The same request in Python
The Python version is a thin wrapper around the same HTTP contract. Use requests with a shared Session so connection pooling works across the submit and every poll. Wrap it in a function so the rest of your code never touches raw HTTP.
import os
import time
import requests
BASE = "https://api.hiapi.ai/v1"
HEADERS = {
"Authorization": f"Bearer {os.environ['HIAPI_API_KEY']}",
"Content-Type": "application/json",
}
session = requests.Session()
def submit_video(prompt: str, duration: int = 5, fast: bool = True) -> str:
body = {
"model": "kling-2.5-turbo/text-to-video",
"prompt": prompt,
"duration": duration,
"fast": fast,
}
resp = session.post(f"{BASE}/video/generations", json=body, headers=HEADERS, timeout=15)
resp.raise_for_status()
return resp.json()["id"]
def poll_video(job_id: str, timeout_s: int = 600, interval_s: float = 3.0) -> dict:
deadline = time.monotonic() + timeout_s
delay = interval_s
while time.monotonic() < deadline:
resp = session.get(f"{BASE}/video/generations/{job_id}", headers=HEADERS, timeout=15)
resp.raise_for_status()
data = resp.json()
status = data.get("status")
if status in ("succeeded", "failed", "cancelled"):
return data
time.sleep(delay)
delay = min(delay * 1.5, 15.0)
raise TimeoutError(f"job {job_id} did not finish in {timeout_s}s")
if __name__ == "__main__":
job = submit_video("A slow dolly-in on a ceramic coffee cup, morning light, steam rising, cinematic")
result = poll_video(job)
print(result)
A few details worth keeping rather than trimming: the timeout on every request so a stuck socket cannot hang your worker, the exponential backoff capped at a sane ceiling, and the separate TimeoutError so your caller can distinguish "still running" from "broken."
If you prefer httpx, the translation is direct — the same method, URL, and JSON body. The transport is not the interesting part; the contract is. Store job_id somewhere durable before you start polling so a restart does not orphan an in-flight generation.
Polling the job and reading the response
The poll loop is where most integration bugs live. Three rules keep it honest.
Rule 1: only terminal states end the loop
You will see transient states — queued, processing — and terminal states such as succeeded, failed, and cancelled. Only terminal states exit the loop. Do not treat an unknown status string as success, and do not break on processing. Log the status on every iteration; when something goes wrong at 2 a.m., that log is your only evidence.
Rule 2: back off, don't spin
A fixed one-second poll is wasteful for jobs that take a minute, and aggressive polling can brush against rate limits when you run batches. Start around two to three seconds and multiply by roughly 1.5x each round, capped at ten to fifteen seconds. The total number of requests for a minute-long job drops from sixty to under fifteen, and your worst-case detection latency is still only the cap.
Rule 3: separate the asset from the metadata
The completed payload contains both. The video itself is delivered as a URL (and often an expiry-bearing one), while everything else — job ID, model, duration, status, timing — is metadata about how you got there. Read the URL, download it promptly, and store it yourself. Generated asset URLs are not a permanent CDN; treat them as a handoff, not a home. Persist the job ID alongside your own copy so you can trace any clip back to the exact request that produced it.
A note on failed
A failed status is a job-level outcome, not a transport error. The HTTP call succeeded; the generation did not. Read the error payload — it may indicate a content policy rejection, an unsupported parameter combination, or an internal failure. Retrying a policy rejection unchanged will simply fail again, so branch on the reason before you consider a retry.
Handling errors, timeouts, and retries
Distinguish two error universes and handle them differently.
Transport-level errors are HTTP failures: connection resets, 5xx responses, rate-limit 429s, and client timeouts. These say nothing about whether the job ran. Retry them with exponential backoff and jitter, and cap the attempts.
Job-level errors are terminal statuses in a successful HTTP response. The job ran and produced a defined outcome. Retrying the poll will not help; if you retry at all, it is a new submit, and only when the failure reason is transient.
Retry the right thing
This is the subtle part. If your submit call times out, you do not know whether the server accepted the job. Blindly resubmitting can produce two generations and two charges. The safer pattern is to make submission idempotent with a client-generated request key that you send as a header or body field, so a duplicate submit returns the original job rather than starting a new one. If the API surface you are using does not offer that, then on a submit timeout, log the prompt hash and duration, and check your recent jobs before resubmitting.
Timeouts at every layer
Set three distinct timeouts and do not conflate them:
- Per-request timeout — short, e.g. ten to fifteen seconds. This bounds a single HTTP call, not the job.
- Overall job deadline — the wall-clock budget for the whole generation. If exceeded, mark the job orphaned, alert, and move on.
- Client-facing timeout — if a user is waiting, decide whether you stream progress or return a job handle to the frontend.
Practical retry policy
Retry 429 and 5xx with backoff up to a small cap. Never retry 400, 401, 403, or 422 — those are bugs in your request, and repeating them just burns quota. Log the response body of every failure; HiAPI error payloads usually name the offending field.
Batch safety
When fanning out many jobs, add a concurrency limit and a jittered start delay. A hundred simultaneous submits is a self-inflicted rate-limit incident. A worker pool of five to ten, each polling independently, is far more stable and easier to observe.
Production checklist before you ship
Before this leaves a notebook, lock these decisions down.
- Timeouts and deadlines. Per-request timeouts on every call, an overall job deadline, and a clear policy for what happens when a job is orphaned. Write them as constants, not magic numbers.
- ID persistence. Store
job_idand your prompt hash before polling starts. This is what makes restarts and audits possible. - Asset storage. Download completed videos to your own storage bucket on success. Never ship a URL you don't control into a frontend or a database as a permanent reference.
- Logging. Log every status transition with job ID, model, duration, preset, and elapsed time. When cost or quality questions come up, this is the dataset that answers them.
- Cost awareness. Billing is by output seconds, so duration is your primary cost lever. The turbo preset keeps latency (and therefore worker occupancy) low. Track seconds generated per feature so cost discussions are grounded in real numbers rather than guesses.
- Idempotency. Client-side request keys for submits, and a rule for what constitutes a safe resubmit.
- Failure UX. Decide now what users see on a job-level failure: a retry button, a fallback prompt, or a plain message. Silently spinning is the worst option.
- Prompt versioning. Keep prompts in config, not inline in handlers, so you can compare outputs across variants without redeploying.
The endpoint itself is small — one submit, one poll. Nearly all the engineering effort lives in the lifecycle around it, and that is exactly where production quality is decided. Start with the model reference for kling-2.5-turbo/text-to-video on HiAPI, then build the harness described here around it.
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