Choose a model, enter your prompt, and see the result.
HiAPI Blog
HiAPI
Generate it with HiAPI
Veo 3.1 Lite turns a single reference image plus a text prompt into a short video clip, and on hiapi it's exposed through the same async task API every model on the platform shares. This guide walks through a real, working request: getting a key, calling POST /v1/tasks, retrieving the result, and the production details (callbacks, idempotency, error handling) you'll want once this moves past a one-off script.
You'll send one image URL and one prompt to the veo-3.1-lite/image-to-video model and get back a hosted .mp4 URL. Everything runs through hiapi's unified task endpoint, so the same request shape works for every video and image model on the platform — only the model id and input fields change.
Before you start:
sk-....localhost paths or private buckets won't work.veo-3.1-lite/image-to-video on the pricing page before running a real (non-test) request, since cost depends on duration and resolution.There's no free-tier or keyless mode — every call needs Authorization: Bearer sk-<your-key>.
hiapi's task API is two calls: create the task, then either poll it or let a callback tell you when it's done. Here's the polling version in Python, using only requests and time:
import time
import requests
API_KEY = "sk-your-key-here"
BASE = "https://api.hiapi.ai/v1/tasks"
HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
payload = {
"model": "veo-3.1-lite/image-to-video",
"input": {
"prompt": "the camera slowly pushes in as steam rises from the cup",
"image_url": "https://example.com/your-reference-image.jpg",
"duration": 6,
"aspect_ratio": "16:9",
},
}
# 1. Create the task
resp = requests.post(BASE, headers=HEADERS, json=payload, timeout=60)
resp.raise_for_status()
task_id = resp.json()["data"]["taskId"]
print("task created:", task_id)
# 2. Poll until it's done
while True:
task = requests.get(f"{BASE}/{task_id}", headers=HEADERS, timeout=30).json()["data"]
status = task["status"]
if status == "success":
video_url = task["output"][0]["url"]
print("done:", video_url)
break
if status == "fail":
raise RuntimeError(task.get("error"))
time.sleep(5)
The same thing in raw curl, if you just want to see the wire format:
curl -s -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer sk-your-key-here" \
-H "Content-Type: application/json" \
-d '{
"model": "veo-3.1-lite/image-to-video",
"input": {
"prompt": "the camera slowly pushes in as steam rises from the cup",
"image_url": "https://example.com/your-reference-image.jpg",
"duration": 6,
"aspect_ratio": "16:9"
}
}'
# then poll:
curl -s https://api.hiapi.ai/v1/tasks/<taskId> \
-H "Authorization: Bearer sk-your-key-here"
A few things worth knowing about the input schema specifically, since it's stricter than a generic "pass whatever" endpoint — extra fields are rejected outright, not silently ignored:
prompt and image_url are the only two required fields. image_url is singular — this model takes exactly one reference image, not an array.duration accepts exactly 4, 6, or 8 (seconds) — no other integers.aspect_ratio accepts auto, 16:9, or 9:16.resolution accepts 720p or 1080p.seed (integer, for reproducibility), generate_audio (boolean), negative_prompt (string).Note the model id is the bare id, veo-3.1-lite/image-to-video — you don't prefix or namespace it further.
Once status is "success", the output video lives at data.output[0].url. That URL is temporary (it carries an expireAt), so download or re-host it immediately rather than storing the hot link.
Polling every 5 seconds works fine for a script, but it's wasteful in a server that's juggling many tasks at once. For production traffic, prefer a callback instead: pass a callback object in the same create-task request, and hiapi will POST the final result to your endpoint instead of you having to ask for it.
payload = {
"model": "veo-3.1-lite/image-to-video",
"input": {
"prompt": "the camera slowly pushes in as steam rises from the cup",
"image_url": "https://example.com/your-reference-image.jpg",
},
"callback": {
"url": "https://yourapp.example.com/hooks/hiapi",
"when": "final",
},
}
when: "final" is the setting that matters here — it means you're notified once, when the task reaches a terminal state (success or fail), not on every intermediate status change. Your webhook handler should verify the task id, then look up whatever local job record you created when you first called POST /v1/tasks.
Idempotency. If your service might retry the create-task call (timeouts, at-least-once delivery, etc.), attach an Idempotency-Key header, up to 255 bytes:
curl -s -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer sk-your-key-here" \
-H "Idempotency-Key: order-42-veo-submit" \
-H "Content-Type: application/json" \
-d '{...}'
Retrying with the same key under the same account returns the original taskId instead of creating a second (billable) task — safe to fire from a retry loop without double-charging a customer.
Polling vs. callbacks, in short: use polling for scripts, notebooks, or low-volume internal tools where you're already blocking on the result. Use callbacks for anything server-side and concurrent — it avoids holding connections open and scales to many in-flight tasks without a polling loop per task.
Error handling. A bad or revoked key returns HTTP 401 with error.code: "permission_denied" — check for that specifically rather than assuming any non-2xx means the video generation itself failed. A malformed input (missing prompt, an out-of-enum duration, an unrecognized field) returns HTTP 400 with error_code: "INVALID_REQUEST" and a message naming the exact offending field — worth surfacing directly in your own logs rather than swallowing it, since the message tells you exactly what to fix. Once a task is accepted, a generation-side failure shows up as status: "fail" on the polled/callback payload, with an error object — treat that as retryable only if the message indicates a transient upstream issue, not a schema problem.
Do I need to pass image_url as a data URI or base64?
No — image_url must be a public HTTP(S) URL that hiapi's servers can fetch. Base64-encoded images aren't accepted by this model.
Can I generate a video without a reference image?
Not with veo-3.1-lite/image-to-video — it's an image-to-video model and image_url is required. If you want text-only generation, look at a text-to-video model instead; the non-lite Veo 3.1 model page lists its available modes.
Why did my request 400 even though I passed all the required fields?
Check for extra fields — this model's input schema rejects anything not in its allowed list (prompt, image_url, duration, aspect_ratio, resolution, seed, generate_audio, negative_prompt). The error message names the exact field it didn't recognize.
How long does generation actually take? It varies with duration and current queue depth, which is exactly why polling with a short sleep or a callback is the right pattern instead of assuming a fixed wait time.
Can I get audio in the output?
Yes, via the generate_audio boolean — check the model docs for the current default and how it affects cost.
What happens if my callback endpoint is down when the task finishes?
Don't rely solely on the callback arriving — keep the taskId you got back from the create call and fall back to a GET /v1/tasks/<id> poll if you haven't heard back within a reasonable window.