What you'll build
One reference-to-video call on hiapi's async /v1/tasks API that turns 1–7 product stills into a short ad clip with native audio. vidu-q3-ad/reference-to-video is the ad-oriented tier of Vidu Q3: you upload product reference images, write a shot description, and the model returns a 3–15 second MP4 with smart cuts, camera moves, and generated sound — there's no separate audio track to supply.
You submit one POST, poll (or take a callback) until the task finishes, then download the MP4. Grab a key from the hiapi dashboard first; every request below uses Authorization: Bearer sk-....
The three things you copy verbatim:
- Submit endpoint:
POST https://api.hiapi.ai/v1/tasks - Poll endpoint:
GET https://api.hiapi.ai/v1/tasks/{taskId} - Model id:
vidu-q3-ad/reference-to-video— the slash and the-adsuffix both matter;vidu-q3-mixandvidu-q4-previeware different endpoints.
The input schema (live-probed)
| field | required | type | notes |
|---|---|---|---|
prompt | yes | string | the shot description; address products by index, e.g. "the watch from reference image 1" |
reference_image_urls | yes | array, 1–7 | public URLs of your product stills (fetched server-side) |
duration | yes | integer, 3–15 | seconds; must be an integer (4.5 is rejected) |
size | no | enum (10 values) | sets both the aspect ratio and the 720/1080 tier; defaults to 1280*720 |
The validator is strict — any field not in that list is rejected outright. Two rejections are worth calling out because they trip up everyone coming from other hiapi video models:
aspect_ratioandresolutionare both rejected. Almost every other hiapi video endpoint uses those; q3-ad does not. Dimensions are controlled only throughsize.reference_audio_urlsis rejected too. Unlike the Vidu Q4 reference-to-video tier (which takes a voice clip), q3-ad has no audio input — its soundtrack is generated natively. (How I confirmed this is in the verification section below.)
Picking size
size is a width*height string from a fixed set. Read it as a grid of five aspect ratios × two quality tiers:
| aspect | 720 tier | 1080 tier |
|---|---|---|
| 16:9 landscape | 1280*720 | 1920*1080 |
| 9:16 vertical | 720*1280 | 1080*1920 |
| 1:1 square | 1280*1280 | 1808*1808 |
| 4:3 | 1280*960 | 1920*1440 |
| 3:4 | 960*1280 | 1440*1920 |
Pick the row for your placement (vertical 720*1280 / 1080*1920 for Reels and TikTok, a landscape row for YouTube pre-roll) and the column for your quality-vs-budget trade-off. Billing is per second of output and the tier sets the per-second rate — see pricing; the minimum charge is a 3-second clip.
Minimal request (curl)
Two steps: submit, then poll.
# 1) Submit — returns a taskId
curl -s -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer sk-your-key" \
-H "Content-Type: application/json" \
-d '{
"model": "vidu-q3-ad/reference-to-video",
"input": {
"prompt": "A product ad of the titanium smartwatch from reference image 1: it rotates slowly on a dark reflective pedestal while a hard strip of light sweeps across the brushed case from left to right, the screen stays dark, the camera holds a low hero angle. Commercial lighting, high contrast, macro detail.",
"reference_image_urls": [
"https://your-cdn.com/watch-front.png",
"https://your-cdn.com/watch-side.png"
],
"duration": 5,
"size": "1280*720"
}
}'
# -> {"data":{"taskId":"..."}}
# 2) Poll until data.status is "success" or "fail"
curl -s https://api.hiapi.ai/v1/tasks/THE_TASK_ID \
-H "Authorization: Bearer sk-your-key"
# success -> data.status == "success", and data.output[0].url is the MP4
The reference image URLs must be publicly reachable — the task fetches them on the server side, so localhost or signed URLs that expire in seconds won't work.
Python: submit, poll, download
import time
import requests # or use urllib/subprocess to stay dependency-free
API = "https://api.hiapi.ai/v1/tasks"
HEADERS = {"Authorization": "Bearer sk-your-key", "Content-Type": "application/json"}
def submit():
payload = {
"model": "vidu-q3-ad/reference-to-video",
"input": {
"prompt": "Ad shot of the sneaker from reference image 1 spinning on a lit turntable, "
"slow push-in, soft rim light, product-hero framing.",
"reference_image_urls": ["https://your-cdn.com/sneaker.png"],
"duration": 5,
"size": "1080*1920", # vertical 1080p for Reels / TikTok
},
}
body = requests.post(API, headers=HEADERS, json=payload, timeout=60).json()
task_id = (body.get("data") or {}).get("taskId")
if not task_id:
raise RuntimeError(f"submit rejected: {body}") # 400s land here
return task_id
def wait(task_id, timeout_s=600, interval=5):
deadline = time.time() + timeout_s
while time.time() < deadline:
data = requests.get(f"{API}/{task_id}", headers=HEADERS, timeout=30).json().get("data") or {}
status = data.get("status")
if status == "success":
return data["output"][0]["url"]
if status == "fail":
raise RuntimeError(f"task failed: {data.get('error')}")
time.sleep(interval)
raise TimeoutError(task_id)
url = wait(submit())
mp4 = requests.get(url, timeout=120).content # download now — output URLs expire
open("ad.mp4", "wb").write(mp4)
A Q3-ad render usually finishes in a minute or two; a 5-second poll interval is plenty. The output URL is temporary (it carries an expireAt), so download the bytes as soon as the task succeeds instead of hotlinking it into a page.
Production patterns
Callback instead of polling. For a server, attach a top-level callback so you don't hold a poll loop open:
{
"model": "vidu-q3-ad/reference-to-video",
"input": { "prompt": "...", "reference_image_urls": ["..."], "duration": 5 },
"callback": { "url": "https://your-app.com/hooks/hiapi", "when": "final" }
}
when only supports "final" — the hook fires once, when the task reaches a terminal state, and its body is the same payload you'd get from GET /v1/tasks/{taskId}. A hook can be retried, so dedupe on taskId, and keep a slow poll as a fallback in case a delivery is dropped.
Branch on the body, not the HTTP status. A failed render still comes back HTTP 200; the failure lives in data.status == "fail" with details in data.error. Only request-level rejections (a bad field, a missing key, no auth) use a 4xx, and those return {"code":400,"error_code":"INVALID_REQUEST","message":"...","data":null}.
Idempotency. Every submit creates a new billable task, so store the taskId the instant you receive it and re-poll that id on retry. Never resubmit because a single poll request timed out.
What I verified
I probed the live endpoint with invalid values, which bounce at validation for free — no task is created and nothing is billed (an invalid value fails before any render starts, which is safer than omitting a field). The real responses are the schema:
- Empty-ish body →
reference_image_urls: missing required field ...; duration: missing required field .... Soprompt,reference_image_urls, anddurationare all required. duration: 99999→maximum: got 99,999, want 15;duration: 1→minimum: got 1, want 3;duration: 4.5→got number, want integer. Duration is an integer in 3–15.reference_image_urlswith 8 entries →maxItems: got 8, want 7. The cap is 7.size: "9000p"→value must be one of '1280*720', '1280*960', '1280*1280', '960*1280', '720*1280', '1920*1080', '1920*1440', '1808*1808', '1440*1920', '1080*1920'.aspect_ratioandresolution→ bothadditional properties ... not allowed. They are not q3-ad fields; usesize.reference_audio_urlsas a well-formed array →additional properties 'reference_audio_urls' not allowed. (Pass it as a non-array and a separate media check fires first — "must be an array" — which is misleading: the field genuinely isn't in the schema.) There is no voice-reference input on this tier; audio is produced natively.
This is an integration reference, so there are no visual-quality claims — I did not run a paid render for this guide. For what a successful output[0].url looks like, the model page below hosts the endpoint's own example: a real MP4 with an audio track.
Related guides
- Vidu Q3 Ad reference-to-video model page — spec, the example ad prompt, and a try-online box.
- Vidu Q4 reference-to-video API guide — the higher tier: up to 4K, 1–15 reference images, and the
reference_audio_urlsvoice field q3-ad doesn't have. - Seedance 2.5 reference-to-video API guide — another reference-to-video endpoint, if you're comparing models.
- AI video camera-movement prompts — shot language that reads well in the
promptfield. - hiapi quickstart and pricing.
FAQ
How do I make the clip portrait vs landscape? Through size, not aspect_ratio. Use 720*1280 or 1080*1920 for vertical, 1280*720 or 1920*1080 for landscape.
Can I supply a voiceover or sound reference? Not on this tier — reference_audio_urls is rejected. Q3-ad generates native audio on its own. If you need to drive audio from a reference clip, use the Vidu Q4 reference-to-video tier.
How many product images can I send? One to seven, in reference_image_urls, and reference them by index in the prompt ("the bottle from reference image 2").
My task returned 200 but there's no video — why? Read data.status. A "fail" returns HTTP 200 with the reason in data.error; only a malformed request gets a 4xx.
What does it cost? Per second of output, and the size tier (720-class vs 1080-class) sets the per-second rate. The minimum is a 3-second clip. See pricing for current rates.




