> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nunchux.ai/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Authenticate every request with your API key in the `X-API-Key` header or `Authorization: Bearer`. Keys start with `sk-nunchux-`.
> Image generation is synchronous: the image is in the response.
> Video generation is asynchronous: submit the job, poll its task until it reaches a terminal status, then download the output promptly, because output URLs expire.

# HappyHorse

> Alibaba Cloud's HappyHorse video models for text-to-video, image-to-video and reference-to-video, with a soundtrack on every clip.

# HappyHorse

HappyHorse is Alibaba Cloud's second video model line, beside Wan. Each version generates a clip from a text prompt, from a still image, or from a set of reference images, and always scores the clip itself. Generation is asynchronous. You submit a task, poll it until the status is terminal, then download the clip. The route mirrors Alibaba Cloud's own API, so the request and response shapes are the provider's.

<Note>
  Best for

  * Sound without a switch. Every clip arrives with a generated soundtrack, so there is no audio decision to make.
  * Reference-driven scenes. Up to nine reference images carry a subject into new footage.
  * Draft passes. HappyHorse 1.1 sells a 480P tier that 1.0 does not.
  * Steady camera work. Tracking shots, slow push-ins and pans, with the motion described in the prompt.
</Note>

## Models

| Model | Model ID | Tasks | Resolution | Duration | Reference images |
| - | - | - | - | - | - |
| HappyHorse 1.1 | `happyhorse-1.1-t2v`, `happyhorse-1.1-i2v`, `happyhorse-1.1-r2v` | t2v, i2v, r2v | 480P, 720P, 1080P | 3 to 15 s | Up to 9 |
| HappyHorse 1.0 | `happyhorse-1.0-t2v`, `happyhorse-1.0-i2v`, `happyhorse-1.0-r2v` | t2v, i2v, r2v | 720P, 1080P | 3 to 15 s | Up to 9 |

Durations are whole seconds. Each version has one model ID per task. Both versions take the same request shape. Only the `model` value changes.

HappyHorse 1.1 is the current line, and the only one with a 480P tier. Use it for drafts and for the final render.

HappyHorse 1.0 is the earlier line at 720P and 1080P.

Image-to-video takes a first frame only. There is no end frame. Reference-to-video takes images only. There is no container for a reference clip or a reference track.

## Endpoints

| Step | Route |
| - | - |
| Submit | `POST /v1/alibaba/services/aigc/video-generation/video-synthesis` |
| Poll | `GET /v1/alibaba/tasks/{task_id}` |

HappyHorse shares the two routes and the body shape of [Wan](/partner-models/wan). The tier goes in `parameters.resolution`, and frames and references go in `input.media[]`.

## Request

Send your API key in the `X-API-Key` header. See [Authentication](/authentication). Set `Content-Type: application/json`. The examples also send a `User-Agent` header that names your application.

<ParamField body="model" type="string" required>
  The model ID from the table above. The ID names both the version and the task.
</ParamField>

<ParamField body="input" type="object" required>
  The prompt and the media items.

  <Expandable title="properties">
    <ParamField body="prompt" type="string" required>
      The text prompt. All tasks. Describe the action and the camera movement. On reference-to-video, name a reference by its position in `media[]`: `[Image 1]`, `[Image 2]` and so on, with the brackets.
    </ParamField>

    <ParamField body="media" type="array">
      The media items for image-to-video and reference-to-video. Omit it for text-to-video. A first frame and a reference set cannot appear in the same body.

      <Expandable title="item properties">
        <ParamField body="type" type="string" required>
          What the item is.

          * `first_frame`: the image the clip starts on. Image-to-video. One item.
          * `reference_image`: a reference image. Reference-to-video. 1 to 9 items.
        </ParamField>

        <ParamField body="url" type="string" required>
          The URL of the image. The provider fetches it, so the URL must be reachable from the internet.
        </ParamField>
      </Expandable>
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="parameters" type="object">
  The generation controls. All tasks read all three fields.

  <Expandable title="properties">
    <ParamField body="resolution" type="string" default="1080P">
      The output tier. `480P`, `720P` or `1080P` on HappyHorse 1.1. `720P` or `1080P` on HappyHorse 1.0. The default is the most expensive tier, so name the one you want.
    </ParamField>

    <ParamField body="duration" type="integer" default="5">
      The clip length in whole seconds, 3 to 15. You are billed per second of output.
    </ParamField>

    <ParamField body="watermark" type="boolean" default="true">
      An omitted flag yields a clip with the text Happy Horse in the lower-right corner. Send `false` for a clean frame.
    </ParamField>
  </Expandable>
</ParamField>

There is no audio parameter. The model scores every clip, and no field turns that off.

## Response

### Submit

A 200 status means that the provider accepted the task. It does not mean that the clip is ready.

<ResponseField name="output" type="object" required>
  <Expandable title="properties">
    <ResponseField name="task_id" type="string" required>
      The task ID. Poll `GET /v1/alibaba/tasks/{task_id}` with it.
    </ResponseField>

    <ResponseField name="task_status" type="string" required>
      `PENDING` on a new task.
    </ResponseField>
  </Expandable>
</ResponseField>

### Poll

<ResponseField name="output.task_status" type="string" required>
  `PENDING` or `RUNNING` while the task runs. `SUCCEEDED`, `FAILED` or `CANCELED` when it ends. Poll until you read one of the three terminal values.
</ResponseField>

<ResponseField name="output.video_url" type="string">
  The URL of the clip. Present only when `task_status` is `SUCCEEDED`. The task ID and the URL expire after 24 hours. Download the clip as soon as the task succeeds.
</ResponseField>

<ResponseField name="output.code" type="string">
  The failure code. Present when the task failed.
</ResponseField>

<ResponseField name="output.message" type="string">
  The failure message. Read it before you resubmit.
</ResponseField>

## Example

Submit, poll, download. Swap the `model` value for the version and task you want.

<CodeGroup>
  ```bash cURL theme={"system"}
  BASE=https://api.nunchux.ai

  # 1. Submit
  SUBMIT=$(curl -sS --fail-with-body "$BASE/v1/alibaba/services/aigc/video-generation/video-synthesis" \
    -H "X-API-Key: $NUNCHUX_API_KEY" \
    -H "Content-Type: application/json" \
    -H "User-Agent: YourApp/1.0" \
    -d '{
      "model": "happyhorse-1.1-t2v",
      "input": { "prompt": "a fox trotting through a snowy forest at dawn" },
      "parameters": { "resolution": "720P", "duration": 5, "watermark": false }
    }') || { echo "submit failed: $SUBMIT" >&2; exit 1; }
  TASK=$(jq -er '.output.task_id' <<<"$SUBMIT") || { echo "no task_id in: $SUBMIT" >&2; exit 1; }

  # 2. Poll until the status is terminal
  DELAY=5
  for _ in $(seq 1 90); do
    RESP=$(curl -sS "$BASE/v1/alibaba/tasks/$TASK" \
      -H "X-API-Key: $NUNCHUX_API_KEY" \
      -H "User-Agent: YourApp/1.0")
    STATUS=$(jq -r '.output.task_status // ""' <<<"$RESP")
    case "$STATUS" in SUCCEEDED|FAILED|CANCELED) break ;; esac
    sleep "$DELAY"; DELAY=$(( DELAY * 2 > 30 ? 30 : DELAY * 2 ))
  done

  # 3. Check for failure
  [ "$STATUS" = "SUCCEEDED" ] || { echo "task $STATUS: $(jq -r '.output.message // "no message"' <<<"$RESP")" >&2; exit 1; }

  # 4. Download
  curl -sSL -o happyhorse.mp4 "$(jq -r '.output.video_url' <<<"$RESP")"
  ```

  ```python Python theme={"system"}
  # pip install requests
  import os, time, requests

  BASE = "https://api.nunchux.ai"
  HEADERS = {
      "X-API-Key": os.environ["NUNCHUX_API_KEY"],
      "User-Agent": "YourApp/1.0",
  }

  # 1. Submit
  submit = requests.post(
      f"{BASE}/v1/alibaba/services/aigc/video-generation/video-synthesis",
      headers=HEADERS,
      json={
          "model": "happyhorse-1.1-t2v",
          "input": {"prompt": "a fox trotting through a snowy forest at dawn"},
          "parameters": {"resolution": "720P", "duration": 5, "watermark": False},
      },
  )
  if not submit.ok:
      raise RuntimeError(f"submit failed: HTTP {submit.status_code} {submit.text}")
  task = submit.json()["output"]["task_id"]

  # 2. Poll until the status is terminal
  delay = 5
  for _ in range(90):
      data = requests.get(f"{BASE}/v1/alibaba/tasks/{task}", headers=HEADERS).json()
      status = data.get("output", {}).get("task_status", "")
      if status in ("SUCCEEDED", "FAILED", "CANCELED"):
          break
      time.sleep(delay)
      delay = min(delay * 2, 30)
  else:
      raise TimeoutError("HappyHorse task did not reach a terminal status")

  # 3. Check for failure
  if status != "SUCCEEDED":
      raise RuntimeError(f"HappyHorse task {status}: {data['output'].get('message')}")

  # 4. Download
  with requests.get(data["output"]["video_url"], stream=True, timeout=300) as r:
      r.raise_for_status()
      with open("happyhorse.mp4", "wb") as f:
          for chunk in r.iter_content(1 << 14):
              f.write(chunk)
  ```
</CodeGroup>

To animate a still, add a `first_frame` item to `input.media[]` and send an `-i2v` model ID. To carry a subject into a new scene, add `reference_image` items and send an `-r2v` model ID. A first frame and a reference set are separate tasks. The bodies below replace the submit body in the example.

<CodeGroup>
  ```json Image-to-video theme={"system"}
  {
    "model": "happyhorse-1.1-i2v",
    "input": {
      "prompt": "the fox turns and trots toward the camera",
      "media": [
        { "type": "first_frame", "url": "https://example.com/frame.jpg" }
      ]
    },
    "parameters": { "resolution": "720P", "duration": 5, "watermark": false }
  }
  ```

  ```json Reference-to-video theme={"system"}
  {
    "model": "happyhorse-1.1-r2v",
    "input": {
      "prompt": "the person from [Image 1] walks through the doorway from [Image 2], slow tracking shot",
      "media": [
        { "type": "reference_image", "url": "https://example.com/ref-1.jpg" },
        { "type": "reference_image", "url": "https://example.com/ref-2.jpg" }
      ]
    },
    "parameters": { "resolution": "720P", "duration": 5, "watermark": false }
  }
  ```
</CodeGroup>

## Tips

* Say what moves and how the camera follows it. The prompt drives both.
* Send `watermark: false` on every request unless you want the mark. The vendor's default adds it.
* Send `resolution` and `duration` on every request. The defaults are 1080P and 5 seconds, and 1080P is the most expensive tier.
* Draft at 480P on HappyHorse 1.1, then re-run the prompt at the tier and length you need. You are billed per second of output, and 1080P costs more per second than 720P.
* Give a reference set images that agree on the subject. Images that disagree pull the result in different directions.
* Name the references in the prompt when the set holds different subjects, so each image has a stated role in the shot.
* Poll every 5 seconds at first, then back off to 30 seconds. Polls are free and never use a concurrency slot.

## Errors and limits

A 200 status on submit means that the provider accepted the task. It does not mean that the task succeeded. A task that fails later ends with `task_status` set to `FAILED` or `CANCELED`, and `output.message` explains why. Read the message before you resubmit.

A failed submit returns an HTTP error status. The body carries `code`, `message` and `request_id`. See [Error codes](/errors).

A 5xx status or a timeout on submit does not tell you whether the task was created. Never resubmit blindly. If the submit returned a `task_id`, poll it. If it returned nothing, check your credits before you try again.

Alibaba Cloud applies these limits to the images you attach:

* A first frame on image-to-video: at least 300 pixels on each side, an aspect ratio between 1:2.5 and 2.5:1, JPEG, JPG, PNG or WEBP, up to 20 MB.
* A reference image on reference-to-video: at least 400 pixels on the short side, JPEG, JPG, PNG or WEBP, up to 20 MB, one to nine images per request.

Every task counts toward the simultaneous-jobs cap of your plan. See [Rate limits](/rate-limits). Rates are per second of output and depend on the tier. See [Credits & pricing](/credits-pricing) and the [Partner models overview](/partner-models/overview).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.