Home › Guides › Kling v3 Python tutorial

Generate Kling v3 videos from Python, end to end

Updated 2026-10-02

This tutorial takes a Kling v3 video request from nothing to a saved MP4 in Python, then adds the pieces a script needs to survive real use: a poll deadline, error handling and a one-line tier switch. It uses plain requests against VideoRouter's POST /videos, so there is no SDK to install. If you want to see the raw HTTP first, the curl walkthrough covers the same calls.

Setup

pip install requests
export LLMR_API_KEY="llmr_sk_live_..."

Use a key with a small monthly cap while you experiment. Create one if you have not already.

Step 1: create the job

import os, time, requests

BASE = "https://videorouter.sh/api/v1"
H = {"Authorization": f"Bearer {os.environ['LLMR_API_KEY']}"}

r = requests.post(f"{BASE}/videos", headers=H, timeout=30, json={
    "model": "kling-v3.0-std",
    "prompt": "a red kite rising over a windy beach, slow upward camera tilt",
    "duration_secs": 5,
    "aspect_ratio": "16:9",
})
r.raise_for_status()
job = r.json()
print(job["id"], job["status"])   # status starts as "queued"

The response is a job object, not a video. You are charged at this point, from the requested duration, so choose it deliberately. Supported durations and sizes depend on the model; check the page for each tier in the models catalog.

Step 2: poll with a deadline

def wait(job_id: str, deadline_s: int = 900, every_s: float = 5) -> dict:
    end = time.monotonic() + deadline_s
    while time.monotonic() < end:
        j = requests.get(f"{BASE}/videos/{job_id}", headers=H, timeout=30).json()
        if j["status"] in ("completed", "failed"):
            return j
        time.sleep(every_s)
    raise TimeoutError(f"job {job_id} still running after {deadline_s}s")

job = wait(job["id"])

Polling is free, so a five-second interval is fine. The deadline matters: without one, a stuck job blocks your script forever. The states are queued, in_progress, then completed or failed.

Step 3: download and save

if job["status"] != "completed":
    raise RuntimeError(job.get("error"))

url = job["data"][0]["url"]
with requests.get(url, stream=True, timeout=(5, 120)) as resp:
    resp.raise_for_status()
    with open("kite.mp4", "wb") as f:
        for chunk in resp.iter_content(1 << 20):
            f.write(chunk)

Treat the URL as a delivery link and save the file immediately. If you serve it to users, copy it into your own storage instead of linking to the upstream URL.

Step 4: switch tiers

Kling v3.0 tiers are separate model ids: kling-v3.0-std, kling-v3.0-pro and kling-v3.0-4k. Switching is a change of one string, which makes an iterate-cheap, promote-later workflow easy to script:

def render(prompt: str, tier: str = "std", secs: int = 5) -> dict:
    model = {"std": "kling-v3.0-std",
             "pro": "kling-v3.0-pro",
             "4k":  "kling-v3.0-4k"}[tier]
    r = requests.post(f"{BASE}/videos", headers=H, timeout=30, json={
        "model": model, "prompt": prompt, "duration_secs": secs})
    r.raise_for_status()
    return wait(r.json()["id"])

draft = render("a red kite rising over a windy beach", "std")
# review the draft, then promote the approved prompt:
final = render("a red kite rising over a windy beach", "pro")

The tier decision itself, including when 4K is worth it, is covered in the Std vs Pro vs 4K guide. Promoting a shot is a new job and a new charge, so use the cheapest tier for prompt iteration and keep the expensive one for approved shots. Live per-second rates are on the pricing page.

Step 5: handle errors properly

Errors use an OpenAI-style body, {"error": {"message", "type", "code"}}. Add a small wrapper so each class of failure is handled intentionally:

def create(payload: dict) -> dict:
    for attempt in range(4):
        r = requests.post(f"{BASE}/videos", headers=H, json=payload, timeout=30)
        if r.ok:
            return r.json()
        if r.status_code == 429:
            time.sleep(float(r.headers.get("Retry-After", 2 ** attempt)))
            continue
        if r.status_code in (500, 502, 503, 504):
            time.sleep(2 ** attempt)
            continue
        err = r.json().get("error", {})
        raise RuntimeError(f"{r.status_code} {err.get('code')}: {err.get('message')}")
    raise RuntimeError("retries exhausted")

Retry 429 after Retry-After and server-class errors with backoff, which VideoRouter documents as not billed when every upstream host failed. Never retry 400, 401, 402 or 403; those need a change on your side. Notice that this wrapper does not catch connection errors on create: if the connection drops after the server accepted the job you cannot know whether it exists, and a blind retry could be a second charge.

Optional: prefer a host

Add a host to the model id, such as kling-v3.0-std/novita, to prefer that host. It is a soft preference and can still fall back. For a strict pin, send "provider": {"only": ["novita"], "allow_fallbacks": False} in the body; a rejection then returns an error instead of trying elsewhere. Check the model page for which hosts currently serve each tier.

Checklist before you loop this over many prompts

A script that works for one prompt can become expensive the moment it sits inside a loop, because every iteration creates a billed job. These habits keep that loop safe.

To add a start frame, see the image-to-video guide; for character animation from a reference clip, see the Motion Control guide. The shared basics live in the quickstart.

Frequently asked questions

Which model ids do I use for Kling v3 in Python?

kling-v3.0-std, kling-v3.0-pro and kling-v3.0-4k. They are separate model ids passed in the model field, so switching tier is a one-string change.

How do I know when a Kling job is finished?

Poll GET /videos/{id} until status is completed or failed. Polling is free; use a deadline so a stuck job cannot block your script.

Where is the finished video?

In data[0].url on the completed job. It is a delivery link, so download it right away or copy it to your own storage.

Is it safe to retry a failed create request?

For 429 and 5xx responses yes, after waiting. Do not blindly retry a dropped connection on create, because the job may already exist and be billed.

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 →