Tutorial
How to Call veo-3.1-lite Text-to-Video via the HiAPI API: curl and Python
A working request, prompt-to-clip flow, and async polling pattern you can copy
What veo-3.1-lite/text-to-video gives you
veo-3.1-lite/text-to-video is a text-to-video endpoint on HiAPI: you send a text prompt, and the model returns a generated video clip. The "lite" in the name is the signal that matters when you're choosing a model. A lite tier is built for throughput and fast iteration rather than maximum fidelity, so it's a good default when you need many short clips, when you're prototyping a feature before committing to a heavier model, or when the clip is one part of a larger automated pipeline where per-clip latency and volume matter more than cinematic polish.
Reach for a heavier text-to-video model when the clip is the product itself — hero shots, marketing footage, anything a human will scrutinize frame by frame. Reach for veo-3.1-lite/text-to-video when the clip is an ingredient: draft previews, background loops, storyboard roughs, or a first-pass render you'll refine later.
Two properties of the endpoint shape everything that follows:
- It is asynchronous. A generation request does not hand you a playable file in the same response. You submit a job, receive a reference to it, and then poll until the job is finished and an output URL is available.
- It is prompt-driven. There is no input image or source video in this flow — the text you send is the entire creative brief, so prompt quality is the main lever you control.
The rest of this guide is a working walkthrough: build the request, submit it with curl, port it to Python, poll correctly, and handle the failures you'll actually hit.
Prerequisites and authentication
Before your first call, make sure you have:
- An API key from your HiAPI account. Treat it as a secret with the same care as a database password.
- A way to send HTTP requests:
curlfor the quick checks, Python 3 with therequestslibrary for anything scripted. - A place to store the finished clip, since the output arrives as a URL you'll download.
The key travels in an HTTP header on every request. Keep the header name and the Bearer prefix exactly as shown; a missing or malformed header is the single most common cause of a first-call failure.
Keep the key out of source control
Never paste a live key into a file you commit. Use an environment variable instead:
export HIAPI_API_KEY="your-key-here"
Then read it at runtime. If you're working in a team or a CI pipeline, store the key in your secret manager and inject it as an environment variable at deploy time. If a key is ever exposed — committed by accident, pasted into a ticket, printed in a log — rotate it immediately rather than hoping nobody noticed.
A quick sanity check that your environment is set before you start debugging anything else:
test -n "$HIAPI_API_KEY" && echo "key is set" || echo "key is missing"
A working curl request
Here is the shape of a text-to-video submission. Replace the placeholder endpoint base with the one documented in your HiAPI reference, and keep the model identifier exactly as written: veo-3.1-lite/text-to-video.
curl -X POST "$HIAPI_BASE_URL/v1/videos/generations" \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "veo-3.1-lite/text-to-video",
"prompt": "A slow dolly-in on a rain-soaked neon sign at night, shallow depth of field, gentle camera drift, cinematic color"
}'
A successful submission does not return video bytes. It returns a job object: an identifier for the job, a status field that will initially read as something like queued or processing, and metadata about when it was created. That identifier is the handle you use for everything else — you will poll it until the status flips to a terminal state, and only then will the response carry the URL of the generated clip.
If you're testing by hand, capture the whole response rather than eyeballing it:
curl -s -X POST "$HIAPI_BASE_URL/v1/videos/generations" \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"veo-3.1-lite/text-to-video","prompt":"A paper boat drifting down a sunlit gutter, macro lens"}' \
| tee submission.json
Two habits worth forming now. First, always send Content-Type: application/json; a body that looks like JSON but isn't declared as such will be rejected. Second, if a call fails, read the error body before changing anything — HiAPI returns a structured message that usually names the problem (bad key, unknown model, missing field) far more precisely than the status code alone.
A non-2xx status here means the job was never accepted. Don't poll on a failed submission; fix the request first.
The same request in Python
The curl call ports directly. The script below submits a prompt, checks that the submission succeeded, and stores the job reference for the polling step.
import os
import requests
BASE_URL = os.environ["HIAPI_BASE_URL"]
API_KEY = os.environ["HIAPI_API_KEY"]
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
payload = {
"model": "veo-3.1-lite/text-to-video",
"prompt": (
"A single lantern floating on still black water, "
"soft ripples, warm light against cool darkness, slow push-in"
),
}
response = requests.post(
f"{BASE_URL}/v1/videos/generations",
headers=headers,
json=payload,
timeout=30,
)
if response.status_code >= 400:
raise RuntimeError(f"Submission failed: {response.status_code} {response.text}")
job = response.json()
job_id = job["id"]
print(f"submitted job {job_id} with status {job.get('status')}")
A few deliberate choices in that snippet:
json=payloadinstead of a hand-built string. Let the library serialize and set the content type; you avoid an entire class of quoting bugs.- An explicit
timeout. Without one, a stalled connection hangs your script indefinitely. The submission call is quick — a generous timeout here is still short. - A hard failure on non-2xx. Raising immediately keeps the error close to its cause instead of letting a broken job ID propagate into the polling loop.
- The key comes from
os.environ. Nothing secret is written into the file itself.
Wrap this in a function that returns job_id and you have the submission half of any pipeline.
Polling the async job and downloading the clip
Video generation takes time. The submission call returns in a second or two; the actual render does not. If you write code that assumes the first response contains a video URL, it will fail — not because the API is broken, but because you're reading a job that hasn't finished.
The correct pattern is a bounded poll: sleep between checks, back off as the wait grows, and give up after a maximum number of attempts rather than looping forever.
import time
import requests
def wait_for_video(job_id, headers, base_url, max_attempts=60, initial_delay=5):
delay = initial_delay
for attempt in range(1, max_attempts + 1):
r = requests.get(
f"{base_url}/v1/videos/generations/{job_id}",
headers=headers,
timeout=30,
)
if r.status_code >= 400:
raise RuntimeError(f"Poll failed: {r.status_code} {r.text}")
data = r.json()
status = data.get("status")
print(f"attempt {attempt}: {status}")
if status == "completed":
return data
if status in {"failed", "cancelled"}:
raise RuntimeError(f"Job {job_id} ended as {status}: {data}")
time.sleep(delay)
delay = min(delay * 1.5, 30)
raise TimeoutError(f"Job {job_id} did not finish within polling budget")
When the job reaches completed, the response carries the output location — the URL of the finished clip. Download it rather than streaming it into memory if clips are large:
result = wait_for_video(job_id, headers, BASE_URL)
video_url = result["output"]["url"]
with requests.get(video_url, stream=True, timeout=120) as r:
r.raise_for_status()
with open(f"{job_id}.mp4", "wb") as f:
for chunk in r.iter_content(chunk_size=1 << 16):
f.write(chunk)
print("saved", f"{job_id}.mp4")
Two details that matter in production. First, the output URL may be time-limited, so download promptly instead of storing the link and fetching it days later — persist the file, or copy it to your own storage. Second, log the job ID alongside every artifact. When something goes wrong downstream, the job ID is what lets you reconcile your records against what the API actually returned.
Common errors and how to fix them
Most first-run failures fall into a small set of buckets. Here's how each one presents and what to change.
Authentication failures
A 401 or 403 on submission means the key wasn't accepted. Check, in order: is the environment variable actually populated in this shell or process; does the header say Authorization; is the Bearer prefix present with a space after it; has the key been rotated or revoked. A key that works in your terminal but fails in CI almost always means the secret wasn't injected into that environment.
Invalid model identifier
A 400 or 404 naming the model means the string didn't match. The identifier is veo-3.1-lite/text-to-video — the slash and the version are part of it. Copy-paste it rather than retyping; a stray space or a changed version number is enough to miss.
Malformed or empty prompts
An empty prompt, a missing prompt field, or a body sent without Content-Type: application/json will be rejected before generation starts. If you're building prompts dynamically, log the exact string you sent when a call fails — template bugs that produce blank or truncated prompts are common and invisible until you print them.
Polling timeouts
If wait_for_video exhausts its budget, don't immediately resubmit. The job may simply still be running. Check the status once more, confirm your poll interval isn't so long that you're burning your budget on sleep, and confirm your budget is realistic for the clip length you requested. Resubmitting on every timeout is how you accidentally pay to generate the same clip several times.
Rate limits
A 429 means back off. The exponential delay in the polling loop already helps here; apply the same restraint to submissions, and queue work rather than firing bursts. When in doubt, honor any retry hint in the response headers before your own backoff schedule.
Cost and iteration tips before production
Video generation is billed by output — the length of what you produce — so the expensive mistake is producing long clips to test short ideas. The cheapest workflow is the one that never generates a clip you didn't need.
Test prompt structure, not final output. Write and refine prompts against the shortest duration the endpoint supports. Once the composition, motion, and camera language read correctly in a short clip, scale the duration up for the final render. Most prompt iteration is about wording, not length, and short renders answer wording questions just as well.
Keep a prompt journal. Log prompt, duration, and your own verdict for each test. Ten recorded iterations teach you more about what this model responds to than fifty unrecorded ones, and you stop re-testing prompts you already rejected.
Batch deliberately. If you need many clips, queue them and let them run concurrently within your rate limits — but dry-run the batch on a handful of prompts first. Discovering a systematic prompt bug after submitting two hundred jobs is the most avoidable cost there is.
Poll with restraint. Frequent polling doesn't make renders finish faster and can push you into rate limits. A modest initial interval with gentle backoff is the right default.
Fail loudly and early. Every guard in the code above — non-2xx raising on submission, terminal-failure detection in the poll, bounded attempts — exists to stop you from paying for work twice. Keep them in production.
Start with one short prompt and the curl request, confirm the job lifecycle end to end, then move the same flow into your Python pipeline. The full endpoint reference is on HiAPI's model documentation page for veo-3.1-lite/text-to-video.
Related articles
View all articles →-
Tutorial
How to Call hailuo-2.3-fast/image-to-video via the HiAPI API: curl and Python
A first working request for the fast, low-cost image-to-video tier — from model ID to downloaded MP4
9 min read -
Tutorial
How to Use hailuo-2.3/text-to-video via the HiAPI API: curl, Python, and a Working Request
11 min read -
Tutorial
Kandinsky 6.0 Pro Text-to-Video on HiAPI: curl and Python Tutorial
A request-first walkthrough: build the prompt-to-clip call, submit it, poll for completion, and download the result
11 min read