Wan / HappyHorse: video generation API
Generate video with Wan 3.0 and HappyHorse 1.1: parameters, results, and caveats.
| Item | Value |
|---|---|
| Base URL | https://api.hop-base.com |
| Submit a task | POST /api/v1/services/aigc/video-generation/video-synthesis |
| Query a task | GET /api/v1/tasks/{task_id} |
| Key group | Wan: Wan 3.0 Official; HappyHorse: HappyHorse 1.1 Official |
HopBase exposes Wan and HappyHorse video generation through the official API contract, on the original paths. Code written against the official SDK or HTTP API migrates by changing only the base URL and the key — the request body, the response shape, and the paths are unchanged.
Available models
| Model ID | Duration | Resolution | Input | Official price |
|---|---|---|---|---|
wan3.0-video | integer 2-30 s (default 5) | 480P / 720P / 1080P | first / last frame, or the reference group | $0.0441from/ second |
happyhorse-1.1-t2v | integer 3-15 s (default 5) | 480P / 720P / 1080P | prompt only | $0.0662from/ second |
happyhorse-1.1-i2v | integer 3-15 s (default 5) | 480P / 720P / 1080P | exactly 1 first_frame image | $0.0662from/ second |
happyhorse-1.1-r2v | integer 3-15 s (default 5) | 480P / 720P / 1080P | 1-9 reference_image images | $0.0662from/ second |
Wan 3.0 duration also accepts -1 for automatic. Its reference group takes up to 10 reference images, 5 reference videos, 5 reference audio tracks, 1 document, and 1 web link.
Use the exact IDs above. No aliases are registered; unknown model names are rejected. wan3.0-video-prime, happyhorse-1.1-video-edit, and the HappyHorse 1.0 line are not offered.
wan3.0-video and the happyhorse-1.1-* models are served by two separate groups — Wan 3.0 Official and HappyHorse 1.1 Official — so a key reaches a model only if it includes that model's group; one key can include both. Before sending paid work, confirm which models your key reaches with GET /v1/models.
Per-tier prices and full specs are on the model cards: wan3.0-video, happyhorse-1.1-t2v, happyhorse-1.1-i2v, happyhorse-1.1-r2v.
Request parameters
All requests use Authorization: Bearer sk-your-key and Content-Type: application/json. You do not need to send an async header; tasks are always asynchronous.
{
"model": "wan3.0-video",
"input": { "prompt": "...", "media": [{ "type": "first_frame", "url": "..." }] },
"parameters": { "resolution": "1080P", "duration": 5 }
}| Parameter | Required | Type & limits | Default | Description |
|---|---|---|---|---|
model | Required | one of the four IDs above | — | Exact ID |
input.prompt | wan3.0-video: prompt or media-t2v / -r2v: Required-i2v: Optional | string; Wan 3.0 ≤20,000 characters, HappyHorse ≤5,000 | — | See the note below |
input.media | wan3.0-video: prompt or media-i2v / -r2v: Required-t2v: not accepted | array of { "type", "url" }; per-type limits below | — | — |
parameters | Optional | object, see below | — | May be omitted entirely |
HappyHorse truncates Chinese prompts of 2,501-5,000 characters to about 2,500 without an error.
input.media
type | Required | Type & limits | Default | Description |
|---|---|---|---|---|
first_frame | -i2v: required | image; Wan 3.0 ≤1; -i2v exactly 1 | — | Not accepted by -t2v / -r2v |
last_frame | Optional | image; Wan 3.0 only, ≤1 | — | — |
reference_image | -r2v: required | image; Wan 3.0 ≤10; -r2v 1-9 | — | — |
reference_video | Optional | Wan 3.0 only; ≤5, ≤15 s combined | — | Billed on input duration; input + output ≤30 s |
reference_audio | Optional | Wan 3.0 only; ≤5, ≤15 s combined | — | — |
file | Optional | Wan 3.0 only; 1 document, ≤100 MB, ≤50 pages | — | Mutually exclusive with link |
link | Optional | Wan 3.0 only; 1 public web page URL, not data: | — | — |
file accepts docx / doc / xlsx / xls / pptx / ppt / pdf / txt / key / pages / numbers / md.
For Wan 3.0, first / last frame input and the reference group (reference_*, file, link) are mutually exclusive.
url accepts an absolute public http(s) URL or a data: URI (base64). oss:// is rejected — the object belongs to your own cloud account, which the gateway cannot access.
Types, counts, and combinations are checked at submission; file size, dimensions, codec, durations, and page counts are checked by the service during execution. The whole JSON body is capped at 128 MiB including base64, so use URLs for large media.
parameters
| Parameter | Required | Type & limits | Default | Description |
|---|---|---|---|---|
resolution | Optional | 480P / 720P / 1080P | 1080P | Selects the billing tier |
duration | Optional | integer seconds; Wan 3.0: 2-30 or -1 (automatic); HappyHorse: 3-15 | 5 | — |
ratio | Optional | Wan 3.0: adaptive / 16:9 / 4:3 / 1:1 / 3:4 / 9:16; HappyHorse -t2v / -r2v: 16:9 / 9:16 / 1:1 / 4:3 / 3:4 / 4:5 / 5:4 / 9:21 / 21:9; -i2v: not accepted | service default | -i2v output follows the input image |
audio | Optional | boolean; Wan 3.0 only | true | Audio on or off costs the same |
prompt_extend | Optional | boolean; Wan 3.0 only | true | Prompt rewriting |
watermark | Optional | boolean | Wan 3.0: false; HappyHorse: true | See the callout below |
seed | Optional | integer 0-2147483647 | — | — |
A parameter the model does not support returns 400 instead of being ignored. The gateway always sends resolution and duration (defaults above); other omitted fields are not sent, so the service defaults apply.
The official default for HappyHorse watermark is true, which stamps a "Happy Horse" mark on the clip. HopBase does not override the official defaults, so send "watermark": false explicitly if you need a clean output.
Image-to-video example:
{
"model": "happyhorse-1.1-i2v",
"input": {
"prompt": "The camera slowly pushes in, light drifting across the scene",
"media": [{ "type": "first_frame", "url": "data:image/jpeg;base64,..." }]
},
"parameters": { "resolution": "720P", "duration": 5, "watermark": false }
}Response
Submit response
A successful submit returns 200 with the HopBase task ID in the official response shape, in output.task_id (shaped like blt…):
{
"output": {
"task_id": "blt60x4115010d-d361-4bfc-b131-a382f1c400a5",
"task_status": "PENDING"
},
"request_id": "..."
}Task query fields
Poll GET /api/v1/tasks/{task_id}.
| Field | Description |
|---|---|
output.task_id | HopBase task ID, blt… |
output.task_status | PENDING / RUNNING / SUCCEEDED / FAILED |
output.video_url | Output URL on success, valid for 24 hours |
output.orig_prompt | Prompt echoed on success |
output.code / output.message | On failure: code is always TaskFailed; read message for the cause |
model | Model ID the task used |
usage | Metering and charge on success; see "Billing" |
usage.cost / usage.cost_usd / usage.currency | The charge for this task: cost_usd is the USD amount actually deducted from your balance, cost is the same charge in CNY, and currency is always CNY |
output.task_status moves PENDING → RUNNING → SUCCEEDED | FAILED (uppercase); only the last two are terminal, so treat any other value as still running. A typical clip completes in 1-5 minutes; long Wan 3.0 clips take longer. Poll at roughly 15-second intervals.
In progress
{
"output": {
"task_id": "blt60x4115010d-d361-4bfc-b131-a382f1c400a5",
"task_status": "RUNNING"
},
"model": "wan3.0-video"
}Succeeded
On success the response carries the output URL in output.video_url:
{
"output": {
"task_id": "blt60x4115010d-d361-4bfc-b131-a382f1c400a5",
"task_status": "SUCCEEDED",
"video_url": "https://api.hop-base.com/...",
"orig_prompt": "..."
},
"usage": { "video_count": 1, "duration": 5, "SR": 1080, "output_video_duration": 5, "input_video_duration": 0, "ratio": "16:9", "billed_seconds": 5, "billing_bucket": "1080p", "cost": 4.08, "cost_usd": 0.6, "currency": "CNY" }
}Failed
A failed task is never billed. code is always TaskFailed, so read output.message (async task failures):
{ "output": { "task_id": "blt60x...", "task_status": "FAILED", "code": "TaskFailed", "message": "..." } }Minimal example
BASE=https://api.hop-base.com
# 1. Submit
curl "$BASE/api/v1/services/aigc/video-generation/video-synthesis" \
-H "Authorization: Bearer sk-your-key" \
-H "Content-Type: application/json" \
-d '{
"model": "wan3.0-video",
"input": {
"prompt": "Aerial shot of snow mountains above clouds at sunrise"
},
"parameters": {
"resolution": "1080P",
"duration": 5,
"ratio": "16:9"
}
}'
# 2. Poll with the returned output.task_id, about every 15 seconds
curl "$BASE/api/v1/tasks/blt-your-task-id" \
-H "Authorization: Bearer sk-your-key"Notes
The official API accepts almost any parameter at submit time: an invalid resolution still returns a task ID, then fails roughly ten minutes later. HopBase decodes the body strictly and rejects such requests immediately with 400 instead.
- Unknown fields and a second JSON value are rejected before the request goes out. This is deliberate.
- Reference media is downloaded on the official side, not by HopBase. Image hosts that block datacenter IPs (many free image CDNs do) cause the task to fail minutes after submission, so prefer
data:embeds or your own object storage / CDN. - Poll
GET /api/v1/tasks/{task_id}. Note this path has no/v1in front of/api, unlike the video endpoints on other pages. There is no list endpoint; keep track of task IDs on your side. - A task still unfinished 2 hours after creation is failed automatically, and these tasks cannot be cancelled.
output.video_urlis a signed URL onapi.hop-base.com, valid for 24 hours from the moment the task completes.- Querying again returns the same URL without extending it, and an expired URL returns 410, so download results within that window.
usage.ratioechoes the produced aspect ratio and may be raw dimensions (e.g.1632:937) rather than one of the request enum values.
Common errors and fixes
Request errors return {"code":…,"message":…}.
| Status | code | Cause / what to do |
|---|---|---|
| 400 | InvalidParameter | Invalid field or value; fix the field named in message |
| 400 | DataInspectionFailed | Content moderation; rewrite the prompt or replace the media |
| 400 | InvalidFile / InvalidURL | Media file or URL unusable; replace it |
| 413 | InvalidParameter | Request body too large; use URLs, not data: |
| 404 | InvalidParameter | Malformed task ID or task not found |
| 429 | Throttling... | Rate limited; back off and retry |
The 413 and 404 message texts:
request body exceeds the 128 MiB limit (received N bytes)
invalid task ID format
task not found # also returned for another user's keyAny other field (OpenAI's size / n / image, Wan 2.x's img_url / negative_prompt / shot_type, extra keys in media) returns 400:
400 {"code":"InvalidParameter","message":"request body does not match the JSON contract: json: unknown field \"size\""}When a task fails, act on output.message:
| message | What to do |
|---|---|
invalid request parameters: ...... parameters or reference media were rejected ... | Fix it and resubmit |
a reference media file could not be downloaded ... | Use a data: URI or reachable hosting |
... declined by content moderation ...... blocked by content moderation ...... may infringe intellectual property rights ... | Rewrite the prompt or replace the media |
video generation failed ...... stopped before completion ...... finished without a usable video ... | Resubmit; contact support if it repeats |
... did not finish within 2 hours ... | Resubmit; contact support with the task ID if it repeats |
Billing
Submitting a task checks available balance minus the estimates for in-flight tasks minus this request's own estimate; if that falls short you get a 402 with code insufficient_balance. The estimate is released automatically once the task ends. See Video overview for the details shared by every video family. A failed task is never billed.
Both series are billed by video duration in seconds, at a per-second unit price that varies by resolution tier — not by token:
charge = usage.billed_seconds × the rate for the usage.billing_bucket tierusage.SR reports the generated video's resolution tier (480 / 720 / 1080) and is what selects it. The two series charge different rates at the same tier.
They differ in what the billable duration covers:
wan3.0-videois billed on the input video as well as the output — billable duration = input video duration + output video duration — sousage.input_video_durationis added to the billed seconds; it reads0.0when the request carries no video input.- The
happyhorse-1.1-*models are billed on output only, and for reference-to-video the official documentation fixesinput_video_durationat0.
Once the task finishes, the query's top-level usage also carries cost_usd (the USD amount actually deducted from your balance for this task, 0 for an uncharged failure), cost (the same charge in CNY), and currency (always CNY).
Official list prices are on the pricing page. Your own rate is the one shown in the signed-in model catalog, and each request's actual charge is in your usage record.
Next steps
- Video overview — balance holds, timeouts, and result URLs shared by every video model
- MiniMax Hailuo H3 video API
- Error codes and retries — including async task failures
- Concurrency, timeouts, and billing