HiAPI
Blog

Tutorial

How to Use the kling-3.0-omni/transformation API: Video Restyle with curl and Python

Oct 14, 2026
How to Use the kling-3.0-omni/transformation API: Video Restyle with curl and Python
Endpoint
POST /v1/tasks
Model id
kling-3.0-omni/transformation
Input
1 source video + prompt
Resolutions
720p / 1080p / 4k

kling-3.0-omni/transformation is Kling 3.0 Omni's video-to-video model: you hand it one source clip plus a text prompt, and it rewrites the look of that clip — scene, style, subject — while keeping the original motion and timing. On hiapi you call it through the same unified async endpoint as every other generation model: POST /v1/tasks. This guide gives you the live-verified parameter table, a copy-paste curl request, a complete Python client that polls and downloads the result, and the production patterns (callbacks, idempotency, error handling) you need before wiring it into a real app.

Every field name, enum value, and response shape below was checked against the live API.

What you'll build + prerequisites

A tiny program that submits a restyle task, waits for it to finish, and downloads the restyled .mp4. You need two things:

  1. An hiapi API key. Create one in your hiapi dashboard — it looks like sk-.... Every request uses Authorization: Bearer sk-<your-key>.
  2. One publicly reachable source video URL. The model downloads your clip server-side, so it must be a public http(s) URL (your own CDN, R2/S3 with a public object, etc.), not a local file path. Exactly one URL is allowed.

The model and its parameters

The request envelope is {"model": ..., "input": {...}}. The input schema is strict — only these three fields are accepted, and anything extra is rejected with a 400.

Field Type Required Notes
prompt string yes Describes the target style/scene/subject. This is also where you dial restyle strength — there is no numeric strength/fidelity param.
video_urls array yes Exactly one public source video URL (minItems 1, maxItems 1).
resolution string enum yes One of 720p, 1080p, 4k.

A few things that trip people up:

  • There is no duration parameter. Output length and aspect ratio follow the source video (3–15.5 seconds), and you're billed by the length of the final output.
  • The model id carries -omni in the id itself: kling-3.0-omni/transformation (some other Omni endpoints use kling-3.0/...). Use the bare id as the model value.
  • Restyle strength is prompt-driven. Say "subtle repaint, keep facial features and lighting" for a light touch, or "fully re-imagine as a 2D cel-shaded cartoon" for a heavy one.

Minimal working example (curl)

# 1) Create the restyle task
curl -s https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer sk-<your-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "kling-3.0-omni/transformation",
    "input": {
      "prompt": "Restyle the scene as a hand-drawn watercolor animation with soft paper texture and warm dusk light; keep the original camera motion and subject.",
      "video_urls": ["https://your-cdn.example.com/source-clip.mp4"],
      "resolution": "720p"
    }
  }'
# -> {"code":200,"data":{"taskId":"tk-hiapi-01..."},"message":"success"}

# 2) Poll the task until it reaches a terminal state
curl -s https://api.hiapi.ai/v1/tasks/tk-hiapi-01... \
  -H "Authorization: Bearer sk-<your-key>"
# data.status progresses: queued -> handling -> archiving -> success
# on success, data.output[0].url is the restyled .mp4

The task id you poll is data.taskId from the create response — not a top-level id. The status strings are queued / handling / archiving / success (terminal) or fail; don't assume succeeded/completed.

Complete Python client (create → poll → download)

import os
import time
import requests

API_KEY = os.environ["HIAPI_KEY"]            # your sk-... key
BASE = "https://api.hiapi.ai"
HEADERS = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
}


def create_restyle(source_video_url: str, prompt: str, resolution: str = "720p") -> str:
    body = {
        "model": "kling-3.0-omni/transformation",
        "input": {
            "prompt": prompt,
            "video_urls": [source_video_url],   # exactly one PUBLIC url
            "resolution": resolution,           # "720p" | "1080p" | "4k"
        },
    }
    r = requests.post(f"{BASE}/v1/tasks", json=body, headers=HEADERS, timeout=30)
    r.raise_for_status()
    data = r.json()
    if data.get("code") != 200:
        raise RuntimeError(f"submit rejected: {data}")
    return data["data"]["taskId"]


def wait_for_video(task_id: str, poll_s: int = 10, timeout_s: int = 900) -> str:
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        r = requests.get(f"{BASE}/v1/tasks/{task_id}", headers=HEADERS, timeout=30)
        r.raise_for_status()
        data = r.json()["data"]
        status = data["status"]
        if status == "success":
            return data["output"][0]["url"]
        if status == "fail":
            raise RuntimeError(f"task failed: {data.get('error')}")
        time.sleep(poll_s)                      # queued / handling / archiving
    raise TimeoutError(f"{task_id} did not finish within {timeout_s}s")


if __name__ == "__main__":
    task_id = create_restyle(
        source_video_url="https://your-cdn.example.com/source-clip.mp4",
        prompt=(
            "Restyle the scene as a hand-drawn watercolor animation, soft paper "
            "texture, warm dusk light; keep the original camera motion and subject."
        ),
        resolution="720p",
    )
    print("task:", task_id)

    url = wait_for_video(task_id)
    print("video url:", url)

    # Output lives on temp.hiapi.ai and expires (~7 days). Download it now.
    clip = requests.get(url, timeout=120).content
    with open("restyled.mp4", "wb") as f:
        f.write(clip)
    print("saved restyled.mp4", len(clip), "bytes")

Run it with your key in the environment:

export HIAPI_KEY=sk-<your-key>
python restyle.py

Production patterns

Use a callback instead of tight polling

For anything beyond a script, register a webhook so you don't hold a polling loop open. callback is a top-level field — a sibling of model and input, never inside input:

{
  "model": "kling-3.0-omni/transformation",
  "input": {
    "prompt": "Restyle as 2D cel-shaded cartoon, bold outlines; keep original motion.",
    "video_urls": ["https://your-cdn.example.com/source-clip.mp4"],
    "resolution": "1080p"
  },
  "callback": { "url": "https://your-app.example.com/hooks/hiapi", "when": "final" }
}
  • callback.when only accepts final (the default). It's queued once when the task reaches a terminal state, and both success and fail trigger it. Delivery is at least once, so the same callback can still arrive more than once (see below).
  • The delivered POST body is identical to the data object you'd get from GET /v1/tasks/<id> — so your handler can read status, output[].url, and error without a re-fetch.
  • Putting callback inside input fails at submit with additional properties 'callback' not allowed (the per-model input schema is strict).

Idempotency and delivery semantics

Callbacks are at least once, not exactly once — the same taskId can arrive more than once. Key your handler on taskId and make the "mark done + store the video" step idempotent. hiapi retries a non-2xx/timeout callback on a schedule of immediately → +1 min → +5 min → +20 min, up to 4 attempts; your endpoint must return HTTP 2xx to stop the retries.

Download outputs immediately

data.output[0].url points at temp.hiapi.ai with an expireAt (unix timestamp, roughly a 7-day TTL). After expiry the object is purged and the URL returns a 404 — re-polling the task returns the same URL but does not regenerate the file. Copy the bytes to your own storage as soon as the task succeeds.

Error handling

Three failure shapes you should branch on:

  • Bad request (HTTP 400) — flat envelope, no task created: {"code":400,"data":null,"error_code":"INVALID_REQUEST","message":"invalid input: resolution: value must be one of '720p', '1080p', '4k'"}
  • Auth failure (HTTP 401) — a nested envelope: {"error":{"code":"permission_denied","type":"hiapi_error", ...}}
  • Failed task — the submit returns 200 + a taskId, but a later poll shows data.status == "fail" with a data.error. Failed tasks are not charged (the reserved amount is refunded on terminal fail). An unreachable or non-downloadable source URL shows up here.

Cost

kling-3.0-omni/transformation is billed per second of output, and the rate scales with the resolution you request (720p $0.143, 1080p $0.193 and 4k $0.479 per second, checked October 21, 2026). See the live hiapi pricing page for the current per-second rate before you batch-run clips — video tasks add up far faster than image ones.

Related reading

FAQ

How do I control how strongly it restyles the clip? Through the prompt, not a parameter. There is no strength, fidelity, or creativity field. Phrase a light edit as "subtle repaint, preserve faces and lighting" and a heavy edit as "fully re-imagine as

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