Search

Search HiAPI models, tools, and resources.

Back to blog
Tutorial

HappyHorse 1.1 Text-to-Video API via HiAPI: a working curl + Python tutorial

hiapitutorialvideorecipe

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 with Authorization: Bearer sk-…; see Authentication if you need a refresher.

Endpoint and request shape

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:

  • ❌ No audio field. Audio is generated natively with the video; there's no toggle. Sending "audio": true returns additional properties 'audio' not allowed.
  • ❌ No 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
  }
}

Minimal working example: curl

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.

Python: a small, production-shaped client

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:

  • Status field. Treat anything other than 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.

Production: prefer callbacks over polling

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:

  • Deduplicate by 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).
  • Return 2xx fast. Do the heavy lift (download mp4, persist, notify the user) in a queue. The webhook handler should ack within a couple of seconds.
  • Keep 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.

Pick the right knobs (resolution × aspect_ratio × duration)

Wantresolutionaspect_ratioduration
Vertical short for social, lowest cost720p9:165
Square preview / thumbnail loop720p1:13
Landscape narrative / ad shot1080p16:95–8
Cinemascope hero clip1080p21:98–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.

FAQ

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.

Generate it with HiAPI

Choose a model, enter your prompt, and see the result.

HiAPI Blog

Related articles

HiAPI

Generate it with HiAPI

Start generating