
HappyHorse 1.1 is Alibaba's text-to-video model: 720p / 1080p clips, 3–15 seconds, native audio in one call (no separate scoring pass). HiAPI exposes it through the same unified async endpoint as every other generative model on the platform, so if you've already called an image model on /v1/tasks the only thing that changes is the model id and a couple of input fields.
This page is the minimum you need to ship a working integration: the exact schema, a copy-paste curl, a Python polling client, and the production callback pattern. It also calls out two non-obvious things the model does not accept (an audio toggle and a seed) so you don't waste a round trip on a 400.
Prerequisites. Grab a key from the HiAPI dashboard and export it as
HIAPI_API_KEY. Every request authenticates withAuthorization: Bearer sk-…; see Authentication if you need a refresher.
Everything goes through one endpoint:
POST https://api.hiapi.ai/v1/tasks
Authorization: Bearer sk-<your-key>
Content-Type: application/json
The body has three top-level keys:
model — happyhorse-1.1/text-to-video (literal model id — leave the slash in).input — model-specific fields. For HappyHorse 1.1 T2V:
prompt (string, required) — describe the clip.resolution (720p | 1080p, default 1080p).aspect_ratio (16:9 | 9:16 | 3:4 | 4:3 | 4:5 | 5:4 | 1:1 | 9:21 | 21:9, default 16:9).duration (integer 3–15, default 5). Cost scales with seconds.callback (optional, object) — callback.url HTTPS endpoint + callback.when: "final". HiAPI POSTs the terminal result there exactly once per task.Two things that look like they should work but don't:
audio field. Audio is generated natively with the video; there's no toggle. Sending "audio": true returns additional properties 'audio' not allowed.seed field. Same 400. If you need batch determinism today, generate multiple takes and pick — there's no seed lever to twist.A minimal valid body:
{
"model": "happyhorse-1.1/text-to-video",
"input": {
"prompt": "A golden retriever running on a beach, slow motion, gentle waves, warm sunset tones",
"resolution": "1080p",
"aspect_ratio": "16:9",
"duration": 5
}
}
Create the task — the response is immediate and just contains a taskId:
curl -sS -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "happyhorse-1.1/text-to-video",
"input": {
"prompt": "A snow mountain lake in morning mist, slow camera push-in, warm cinematic light",
"resolution": "1080p",
"aspect_ratio": "16:9",
"duration": 5
}
}'
# => {"code":200,"data":{"taskId":"tk-hiapi-01KW3..."},"message":"success"}
Then poll until terminal:
TASK_ID=tk-hiapi-01KW3...
curl -sS "https://api.hiapi.ai/v1/tasks/$TASK_ID" \
-H "Authorization: Bearer $HIAPI_API_KEY"
# while .data.status is "handling", sleep a few seconds and retry.
# On success:
# {
# "code": 200,
# "data": {
# "status": "success",
# "model": "happyhorse-1.1/text-to-video",
# "output": [
# {"type":"video","url":"https://temp.hiapi.ai/.../...-0.mp4","expireAt":1783130616}
# ],
# ...
# }
# }
The mp4 URL is temporary (expireAt is a Unix timestamp ~7 days out). Download and persist it as soon as the task succeeds — don't hotlink. If you'd rather have HiAPI manage long-term storage, see the Output Storage docs for promoting outputs from temp to persistent.
A real client needs three things: a create_task that returns the id, a wait_task that polls with a deadline, and a download step. This skeleton has all three and is close to what HiAPI's Python SDK page starts from — keep it small, expand from there.
# pip install requests
import os, time, json, requests
API_BASE = "https://api.hiapi.ai/v1/tasks"
HEADERS = {"Authorization": f"Bearer {os.environ['HIAPI_API_KEY']}"}
def create_task(prompt: str, *, resolution="1080p", aspect_ratio="16:9", duration=5) -> str:
body = {
"model": "happyhorse-1.1/text-to-video",
"input": {
"prompt": prompt,
"resolution": resolution,
"aspect_ratio": aspect_ratio,
"duration": duration,
},
}
r = requests.post(API_BASE, headers={**HEADERS, "Content-Type": "application/json"},
data=json.dumps(body), timeout=60)
r.raise_for_status()
data = r.json()
if data.get("code") != 200:
raise RuntimeError(f"create failed: {data}")
return data["data"]["taskId"]
def wait_task(task_id: str, *, timeout_s: int = 600, poll: int = 5) -> dict:
deadline = time.time() + timeout_s
while time.time() < deadline:
r = requests.get(f"{API_BASE}/{task_id}", headers=HEADERS, timeout=30)
r.raise_for_status()
task = r.json().get("data") or {}
status = task.get("status")
if status == "success":
return task
if status == "fail":
raise RuntimeError(f"task {task_id} failed: {task.get('error')}")
time.sleep(poll)
raise TimeoutError(f"task {task_id} did not finish within {timeout_s}s")
def download(url: str, path: str) -> None:
with requests.get(url, stream=True, timeout=120) as r:
r.raise_for_status()
with open(path, "wb") as f:
for chunk in r.iter_content(1 << 14):
f.write(chunk)
if __name__ == "__main__":
task_id = create_task(
"A neon street at night after rain, reflections on the ground, "
"cyberpunk mood, slow pan",
resolution="720p", aspect_ratio="9:16", duration=5,
)
task = wait_task(task_id)
url = task["output"][0]["url"]
download(url, "out.mp4")
print("saved out.mp4")
A few notes for production:
success or fail as in-progress (the platform currently uses handling).raise_for_status is not enough. HiAPI returns HTTP 200 with a non-200 code for some validation errors — always check data["code"] too. The most common one for a fresh integration is 401 permission_denied, which means the bearer token is wrong or missing — re-export HIAPI_API_KEY and confirm against the dashboard.For anything past a quickstart, hand HiAPI a webhook and let it call you when the task finishes:
{
"model": "happyhorse-1.1/text-to-video",
"input": {
"prompt": "A snow mountain lake in morning mist, slow camera push-in, warm cinematic light",
"resolution": "1080p",
"aspect_ratio": "16:9",
"duration": 5
},
"callback": {
"url": "https://your-domain.com/hiapi/callback",
"when": "final"
}
}
Things to make sure your callback handler does:
taskId. success and fail are both terminal — your service should treat receiving the same taskId twice as a no-op (idempotent write keyed on taskId).GET /v1/tasks/:id as a fallback. If a webhook is missed (deploy, network blip), a reconciliation cron that fetches stuck tasks by id and applies the same handler is the simplest safety net.If you're picking a path: polling is fine for one-off scripts and local debugging, callbacks are what you want anywhere there's real traffic.
| Want | resolution | aspect_ratio | duration |
|---|---|---|---|
| Vertical short for social, lowest cost | 720p | 9:16 | 5 |
| Square preview / thumbnail loop | 720p | 1:1 | 3 |
| Landscape narrative / ad shot | 1080p | 16:9 | 5–8 |
| Cinemascope hero clip | 1080p | 21:9 | 8–12 |
Cost scales with both resolution and duration — exact per-second pricing lives at HiAPI Pricing. When you don't know the right value, leave it off: resolution defaults to 1080p, aspect_ratio to 16:9, duration to 5.
Is there a seed parameter for HappyHorse 1.1 text-to-video?
No. The API rejects seed with additional properties 'seed' not allowed. If you need multiple options to choose from, submit a few tasks with the same prompt — different runs naturally diverge.
How do I turn audio on or off?
You don't — HappyHorse 1.1 generates the soundtrack natively in the same call. There's no audio toggle in the schema; sending one returns a 400. If you need a silent clip, strip audio in post.
What are the valid durations?
Integers from 3 to 15 (seconds). Anything outside that range comes back as duration: minimum: got 0, want 3 or duration: maximum: got 20, want 15. Decimals are also rejected (the field is typed integer).
Which aspect ratios does it support?
Nine: 16:9, 9:16, 3:4, 4:3, 4:5, 5:4, 1:1, 9:21, 21:9. Defaults to 16:9.
How long does the output[].url stay live?
About a week — the response includes an expireAt Unix timestamp. Download the mp4 immediately and re-host it (S3, R2, your CDN), or promote it to persistent storage via the Output Storage API.
What about image-to-video?
HappyHorse 1.1 has an I2V variant on the same endpoint — see HappyHorse 1.1 I2V. Same auth, same async lifecycle, different input schema.
Where's the full async API reference? Create Task for the request envelope, Get Task Detail for the polling endpoint, and the HappyHorse 1.1 T2V model page for the canonical input schema. If you're starting from zero, the Quickstart walks the first call end-to-end.
Key Takeaways