HiAPI
Blog

Tutorial

How to Use veo-3.1-lite/text-to-video via the hiapi API: curl, Python, and a Working Request

Aug 27, 2026
How to Use veo-3.1-lite/text-to-video via the hiapi API: curl, Python, and a Working Request

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 requests installed (pip install requests).
  • Nothing else — text-to-video needs 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

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.

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