veo-3.1-lite/text-to-video generates a short video clip from a text prompt alone — no starting image required. This guide has a copy-pasteable curl and Python example against the real hiapi task API, the exact input schema (confirmed against the live endpoint), and the error shapes you'll actually hit in production.
1. Prerequisites
- A hiapi account and an API key (
sk-...) from the API Keys dashboard. - curl, or Python 3 with
requestsinstalled (pip install requests). - Nothing else —
text-to-videoneeds only a prompt string.
Every generation model on hiapi runs through the same unified async task API: POST /v1/tasks to create a job, then poll or wait for a callback to get the result. veo-3.1-lite/text-to-video is called exactly like any other video model — same auth header, same task lifecycle — only model and input change. The model id is veo-3.1-lite/text-to-video, with the /text-to-video suffix; the same family also ships veo-3.1-lite/image-to-video as a separate model id for animating a source image instead.
2. Minimal runnable example
2.1 Create the task (curl)
curl -s -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer sk-<your-api-key>" \
-H "Content-Type: application/json" \
-d '{
"model": "veo-3.1-lite/text-to-video",
"input": {
"prompt": "a paper boat drifting down a rain-slicked city street at night, neon signs reflecting on wet asphalt",
"duration": 6,
"resolution": "720p",
"aspect_ratio": "16:9",
"generate_audio": true
}
}'
A successful call returns a task id immediately — generation happens asynchronously:
{"code":200,"data":{"taskId":"tk-hiapi-01XXXXXXXXXXXXXXXXXXXXXXXX"},"message":"success"}
2.2 Poll for the result
curl -s https://api.hiapi.ai/v1/tasks/tk-hiapi-01XXXXXXXXXXXXXXXXXXXXXXXX \
-H "Authorization: Bearer sk-<your-api-key>"
While the clip is rendering, status is "handling". Once it finishes:
{
"code": 200,
"data": {
"taskId": "tk-hiapi-01XXXXXXXXXXXXXXXXXXXXXXXX",
"model": "veo-3.1-lite/text-to-video",
"status": "success",
"storage": "temp",
"output": [
{"artifactId": "...", "type": "video", "url": "https://temp.hiapi.ai/.../result.mp4", "expireAt": 1786932195}
]
},
"message": "success"
}
output[0].url is a temporary, expiring link — download the bytes (or promote the output to persistent storage) before expireAt passes.
2.3 Full Python example
import time
import requests
API_KEY = "sk-<your-api-key>"
BASE = "https://api.hiapi.ai/v1/tasks"
HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
def create_task(prompt: str) -> str:
resp = requests.post(BASE, headers=HEADERS, json={
"model": "veo-3.1-lite/text-to-video",
"input": {
"prompt": prompt,
"duration": 6,
"resolution": "720p",
"aspect_ratio": "16:9",
},
}, timeout=30)
resp.raise_for_status()
return resp.json()["data"]["taskId"]
def wait_for_result(task_id: str, poll_seconds: int = 5, timeout_seconds: int = 600) -> str:
deadline = time.time() + timeout_seconds
while time.time() < deadline:
resp = requests.get(f"{BASE}/{task_id}", headers=HEADERS, timeout=30)
resp.raise_for_status()
data = resp.json()["data"]
if data["status"] == "success":
return data["output"][0]["url"]
if data["status"] == "failed":
raise RuntimeError(f"task {task_id} failed: {data}")
time.sleep(poll_seconds)
raise TimeoutError(f"task {task_id} did not finish in {timeout_seconds}s")
if __name__ == "__main__":
tid = create_task("a paper boat drifting down a rain-slicked city street at night")
video_url = wait_for_result(tid)
print(video_url)
3. Input schema
All fields below live under input. The schema is strict — sending a field that doesn't exist returns a 400 (additional properties '<field>' not allowed), which is a fast way to sanity-check a request before it renders.
| Field | Type | Required | Notes |
|---|---|---|---|
prompt | string | yes | Scene description; be specific about subject, motion, and camera behavior. |
duration | integer | no | One of 4, 6, 8 (seconds). |
resolution | string | no | "720p" or "1080p". |
aspect_ratio | string | no | "16:9" or "9:16". |
generate_audio | boolean | no | Include synchronized ambient/sound-effect audio in the render. |
negative_prompt | string | no | Elements to steer the render away from. |
seed | integer | no | Fix for reproducible framing/motion across retries. |
Longer duration and higher resolution both increase render cost — check current per-model pricing on the pricing page before scaling up a batch job.
4. Production patterns
Prefer callbacks over polling at scale
For anything beyond a one-off script, register a callback instead of polling in a loop:
{
"model": "veo-3.1-lite/text-to-video",
"input": {"prompt": "..."},
"callback": {"url": "https://your-app.example.com/webhooks/hiapi", "when": "final"}
}
callback.url must be a reachable http(s) URL, and when only accepts "final" — hiapi POSTs once, when the task reaches a terminal state (success or failed). This avoids burning request quota on a polling loop and gets you the result the moment it's ready, which matters more for video (render times are longer than image generation).
Idempotency and retries
If your job runner can retry a submission (crash, timeout, redeploy), track your own idempotency key alongside the returned taskId before you fire the request, so a retry can check "did I already submit this?" instead of creating a duplicate render and paying for it twice.
Error handling
Two distinct error shapes show up in practice:
- Bad input (
400) — validation errors on the request body:{"code":400,"data":null,"error_code":"INVALID_REQUEST","message":"invalid input: prompt: missing required field \"prompt\""} - Auth/permission failure (
401) — missing, invalid, or under-permissioned key:{"error":{"code":"permission_denied","message":"This API key cannot use the selected model...","request_id":"...","type":"hiapi_error"}}
Branch on the HTTP status code first, then inspect the body — a 400 almost always means a bad field name or value (fix the request), while a 401 means the key itself needs attention (dashboard permissions or a fresh key).
5. Related pages
- veo-3.1-lite/text-to-video model page — live pricing and an in-browser playground to test prompts before wiring up code.
- veo-3.1-lite/image-to-video model page — the image-conditioned sibling model, for animating an existing frame instead of starting from text.
- Unified Async API introduction — the task lifecycle shared by every model on hiapi (images, video, audio).
- Authentication docs — API key format and header details.
- seedance-2.5/text-to-video API guide — a second text-to-video model on the same task API, useful for comparing schemas and pricing.
FAQ
Does veo-3.1-lite/text-to-video need a starting image?
No. It generates purely from the prompt string. If you have a source image to animate, use the separate veo-3.1-lite/image-to-video model id instead.
What resolutions and durations are supported?
resolution is "720p" or "1080p"; duration is 4, 6, or 8 seconds. Sending any other value returns a 400 naming the allowed set.
Can I get audio in the generated clip?
Yes — set "generate_audio": true in input to include synchronized ambient/sound-effect audio.
How do I avoid polling in a loop?
Pass a callback object with your webhook url and "when": "final". hiapi posts to that URL once the task finishes, instead of you re-checking GET /v1/tasks/<id> on a timer.
Why did my request get a 401 instead of a 400?
401 with error_code/code "permission_denied" means the API key itself is invalid or lacks access to the model — check the API Keys dashboard. 400 means the request body is malformed for a key that's otherwise valid.
Is the returned video URL permanent?
No — output[0].url is a temporary link with an expireAt timestamp. Download or promote it to persistent storage before it expires.









