Docs

Video generation

Text-to-video and image-to-video on Google's Veo 3 family. Two API shapes: a one-shot synchronous endpoint that blocks until the clip is ready, and a submit-and-poll async pair for production workloads.

本文件為英文版。如果你需要繁體中文版的快速接入指引,請看:繁體中文快速開始 — 從台灣調用 Claude API

Endpoints: POST /v1/video/generations (synchronous, covered first), and POST /v1/videos + GET /v1/videos/{id} (async, recommended for production). Generation runs 30 seconds to several minutes depending on model and duration — for the sync endpoint, set a generous client timeout (600s is safe).

Text-to-video

Pass a prompt and pick a model. Optional duration (seconds) and aspect_ratio.

import requests

resp = requests.post(
    "https://api.kunavo.com/v1/video/generations",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "model": "veo-3",
        "prompt": "a cinematic dolly-in on a red origami crane unfolding, soft light",
        "duration": 5,
        "aspect_ratio": "16:9",
    },
    timeout=600,  # video generation can take minutes
)
print(resp.json()["data"][0]["url"])

Image-to-video

Add an image_url and Veo animates from it — by default the still becomes the video's first frame.

# Image-to-video: animate a still image. The source image can be an
# https URL, or inline base64 — Kunavo hosts it for the model automatically.
resp = requests.post(
    "https://api.kunavo.com/v1/video/generations",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "model": "veo-3",
        "prompt": "the crane gently lifts off the desk",
        "image_url": "https://your-cdn.com/source.png",
        "duration": 5,
    },
    timeout=600,
)
print(resp.json()["data"][0]["url"])
image_url accepts an https:// URL or an inline data: base64 URI. base64 is decoded and hosted on Kunavo's CDN automatically before the model runs.

First frame, last frame, and references

image_mode controls how Veo uses the image(s). Pass image_urls (an array) to send more than one.

  • frame (default) — one image is the first frame; two images become the first and last frame, and Veo generates the transition between them.
  • reference — up to three images act as style and subject references. They steer the look but never appear as a frame.
# First + last frame: pass two images and Veo fills in the motion.
resp = requests.post(
    "https://api.kunavo.com/v1/video/generations",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "model": "veo-3",
        "prompt": "a smooth transition between the two poses",
        "image_urls": [
            "https://your-cdn.com/first.png",
            "https://your-cdn.com/last.png",
        ],
        "image_mode": "frame",
        "duration": 5,
    },
    timeout=600,
)

Uploading a source frame

When you only have a local file, upload it once via POST /v1/files and reuse the returned permanent URL.

# No public URL? Upload the still first, then animate it.
with open("frame.png", "rb") as f:
    up = requests.post(
        "https://api.kunavo.com/v1/files",
        headers={"Authorization": f"Bearer {API_KEY}"},
        files={"file": f},
    ).json()

resp = requests.post(
    "https://api.kunavo.com/v1/video/generations",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "model": "veo-3",
        "prompt": "slow push-in, volumetric light",
        "image_url": up["url"],
    },
    timeout=600,
)

Parameters

ParamTypeNotes
modelstring (required)A video model slug from /v1/models.
promptstring (required)Text description of the shot.
image_urlstringSingle source image for image-to-video. URL or data: URI.
image_urlsstring[]Multiple source images (Veo). Takes precedence over image_url.
image_modestringVeo: "frame" (default — first/last frame) or "reference" (style refs).
durationintClip length in seconds. Model-dependent (commonly 5–10).
aspect_ratiostringe.g. "16:9", "9:16", "1:1".
resolutionstringWhere the model exposes tiers — e.g. "720p", "1080p".
inputobjectEscape hatch — raw upstream input, bypasses our adapter.

Video models

SlugProviderNotes
veo-3GoogleVeo 3 Fast — cheapest tier, native audio.
veo-3-qualityGoogleVeo 3 Quality — top fidelity.
veo-3-liteGoogleVeo 3 Lite — budget tier for previews.

Always check /models for the live list and per-model pricing.

Latency & timeouts

  • Kunavo polls the upstream task for up to 540 seconds before returning a 504.
  • Fast tiers (Veo 3 Fast / Lite): often 30–90s.
  • Quality tiers and longer durations: 2–5 minutes.
  • Set your HTTP client timeout to at least 600s.
  • On flaky networks or mobile clients, prefer the async API instead — the submit returns in seconds and you poll for the result.

Async API (submit + poll)

Endpoints: POST /v1/videos + GET /v1/videos/{id}. Shape mirrors OpenAI's Sora API so SDKs can swap base URLs.

The sync endpoint above holds an HTTP connection open for up to nine minutes — convenient for one-shot scripts but unreliable on mobile and inefficient on the server. The async pair returns immediately with a vid_* id; you poll GET /v1/videos/{id} until status is completed or failed. Same models, same pricing, same archived URLs.

import requests, time

# 1. Submit — returns immediately with a task id. No long-lived connection.
submit = requests.post(
    "https://api.kunavo.com/v1/videos",
    headers={
        "Authorization": f"Bearer {API_KEY}",
        # Optional: retrying with the same key inside ~24h returns the
        # original task rather than submitting again.
        "Idempotency-Key": "my-task-uuid",
    },
    json={
        "model": "veo-3",
        "prompt": "a cinematic dolly-in on a red origami crane",
        "duration": 5,
        "aspect_ratio": "16:9",
    },
    timeout=60,
).json()

task_id = submit["id"]              # "vid_abc..."
status  = submit["status"]          # "queued"

# 2. Poll until terminal. Recommended cadence: 5s, backing off to 30s.
while status not in ("completed", "failed"):
    time.sleep(5)
    r = requests.get(
        f"https://api.kunavo.com/v1/videos/{task_id}",
        headers={"Authorization": f"Bearer {API_KEY}"},
        timeout=30,
    ).json()
    status = r["status"]

if status == "failed":
    raise RuntimeError(r["error"]["message"])
print(r["output"]["url"])

Response shape

FieldTypeNotes
idstringvid_-prefixed task id. Stable across upstream changes.
objectstring"video".
statusstringqueued | in_progress | completed | failed.
modelstringThe model slug you submitted.
created_atintUnix seconds when we accepted the submit.
completed_atint | nullUnix seconds when terminal — null while pending.
expires_atintUnix seconds after which the result is pruned (~30 days).
progressint0–100. We currently report 0 until terminal, then 100.
outputobject | null{url, urls[], duration_seconds, resolution} on completed.
errorobject | null{code, message} on failed.

Headers & conventions

  • Idempotency-Key (optional, up to 128 chars) — submitting twice with the same key on the same account returns the original task instead of creating a duplicate. Useful for retry loops.
  • POST /v1/videos returns 202 Accepted on a fresh submit and 200 OK when an idempotency key hit replays a previous result.
  • GET /v1/videos/{id} is owner-scoped: querying a task that belongs to another account returns 404 — we never confirm an id exists for someone else.
  • Recommended polling cadence: 5 s with exponential backoff up to 30 s. Tasks usually settle in 30 s – 5 min depending on model and resolution.

When to use which

Sync /v1/video/generationsAsync /v1/videos
Best forOne-off scripts, notebooksProduction, mobile, long-running jobs
NetworkHolds connection up to 9 minSubmit in seconds; poll on your schedule
Failure recoveryLose the response = lose the taskRe-query the id any time before expires_at
ConcurrencyOne server worker per callSubmits release immediately
SDK swapOpenAI /images/generations shapeOpenAI /videos (Sora) shape

Response & storage

Response is OpenAI's standard shape — data[].url. The URL is a permanent files.kunavo.com link: Kunavo archives every result to its own CDN, so unlike raw upstream URLs it never expires. Every clip also shows up in /app/assets.

Where to go next