HiAPI
  • Models
  • Pricing
Search

Search HiAPI models, tools, and resources.

  • Models
  • Pricing
HiAPI

One API, All AI Models

Generate images, video, and audio with leading models through one production-ready API.

Get a free API key

AI Image API

  • All image models
  • GPT Image 2
  • Nano Banana 2
  • Seedream 5.0 Pro
  • Qwen Image 2.0 Pro
  • FLUX 1.1 Pro

AI Video API

  • All video models
  • Seedance 2.5
  • FLUX.3 Video
  • Seedance 2.0
  • Veo 3.1
  • Kling 3.0

AI Audio API

  • All audio models
  • MiniMax Music 2.6
  • MiniMax Music 1.5
  • ElevenLabs v3
  • Text to music
  • Text to speech

Product

  • Model marketplace
  • Playground
  • Pricing
  • Image API Cost Calculator
  • Free GPT Image 2 Generator
  • Free Nano Banana Image Generator
  • Outfit Preview
  • Product Photo Lab

Developers

  • Documentation
  • API Reference
  • Agent Skills
  • LLM integration index
  • Blog

Company

  • About
  • Contact support
  • Terms of Service
  • Privacy Policy

© 2026 hiapi. All rights reserved.

Open source on GitHubPython SDK on PyPI
  • What you'll build
  • The smallest working request
  • curl
  • Python (stdlib only)
  • The full parameter table
  • A note on cost vs duration
  • Getting the result back: polling
  • Getting the result back: callback (production pattern)
  • Designing a robust receiver
  • Errors you will actually hit
  • Related on hiapi
  • FAQ
TutorialJun 26, 20267 min read

Happyhorse 1.1 text-to-video API: a working curl and Python tutorial

hiapiTutorialVideo GenerationHappyhorsehiapi API

Latest models

Explore models

Contents
  • What you'll build
  • The smallest working request
  • curl
  • Python (stdlib only)
  • The full parameter table
  • A note on cost vs duration
  • Getting the result back: polling
  • Getting the result back: callback (production pattern)
  • Designing a robust receiver
  • Errors you will actually hit
  • Related on hiapi
  • FAQ

Generate it with HiAPI

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

HiAPI Blog

Related articles

HiAPI

Generate it with HiAPI

This page walks you through the smallest request that produces a video with happyhorse-1.1/text-to-video, what every input field actually accepts, and the two ways to wait for the result (polling and callback). Everything below has been run against the live API so the request shapes and error messages are verbatim.

What you'll build

A single function that takes a text prompt and returns a downloadable MP4 URL. The model is happyhorse-1.1/text-to-video, served through the unified /v1/tasks endpoint. You submit a task, get a taskId back instantly, and then either poll until it's done or let hiapi POST the final result to your webhook.

Prerequisites:

  • An hiapi API key. New accounts start with $1 of free credit — grab one from the API Keys page.
  • A way to make HTTPS requests. No SDK to install.

Keep the key out of client-side code and out of git. Read it from an env var:

export HIAPI_API_KEY="sk-..."

The smallest working request

The only required input field is prompt. Everything else has a default.

curl

curl -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 calm ocean wave at sunset, slow motion, golden hour"
    }
  }'

Response:

{
  "code": 200,
  "data": { "taskId": "tk-hiapi-01KW23VDG2EZ20J7RQH8S67MNC" },
  "message": "success"
}

That taskId is your handle to the job. Hold on to it.

Python (stdlib only)

import os
import urllib.request
import json

API_KEY = os.environ["HIAPI_API_KEY"]

def submit_task(prompt: str, **input_overrides) -> str:
    body = {
        "model": "happyhorse-1.1/text-to-video",
        "input": {"prompt": prompt, **input_overrides},
    }
    req = urllib.request.Request(
        "https://api.hiapi.ai/v1/tasks",
        method="POST",
        headers={
            "Authorization": f"Bearer {API_KEY}",
            "Content-Type": "application/json",
        },
        data=json.dumps(body).encode("utf-8"),
    )
    with urllib.request.urlopen(req) as resp:
        payload = json.loads(resp.read())
    if payload.get("code") != 200:
        raise RuntimeError(payload)
    return payload["data"]["taskId"]

task_id = submit_task("a calm ocean wave at sunset, slow motion, golden hour")
print(task_id)  # e.g. tk-hiapi-01KW23VDG2EZ20J7RQH8S67MNC

The full parameter table

The schema is small and strict — unknown fields are rejected with a 400, not silently dropped. Here is what the API actually accepts inside input, taken straight from the validation errors:

FieldTypeRequiredAllowed valuesDefault behavior
promptstringyesany non-empty text describing the scene—
aspect_ratiostringno16:9, 9:16, 3:4, 4:3, 4:5, 5:4, 1:1, 9:21, 21:9model default
resolutionstringno720p, 1080pmodel default
durationintegerno3 to 15 (seconds)model default

Things that look obvious but are not accepted today: size, audio, seed, negative_prompt. Sending any of those returns additional properties '<field>' not allowed. If you've worked with other hiapi models — qwen-image-2.0 uses size, z-image uses aspect_ratio — note that happyhorse-1.1/text-to-video follows the aspect-ratio family, not the explicit-size family.

A request that uses every supported knob:

curl -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 vintage paper plane gliding above neon Tokyo at night, cinematic, 24fps",
      "aspect_ratio": "16:9",
      "resolution": "1080p",
      "duration": 8
    }
  }'

A note on cost vs duration

The model is priced per second of generated video — see the pricing page for the current rate. duration is the cost lever, not resolution. Bumping from 5s to 15s triples the bill; flipping from 720p to 1080p does not, at the time of writing. During development, default to duration: 3 and a low resolution; promote to 1080p only on the final approved prompt.

Getting the result back: polling

The simplest way to retrieve output is to GET the task by id until status is terminal.

import time
import urllib.request
import json

def get_task(task_id: str) -> dict:
    req = urllib.request.Request(
        f"https://api.hiapi.ai/v1/tasks/{task_id}",
        headers={"Authorization": f"Bearer {API_KEY}"},
    )
    with urllib.request.urlopen(req) as resp:
        return json.loads(resp.read())["data"]

def wait_for_task(task_id: str, timeout_s: int = 600, poll_interval_s: int = 10) -> dict:
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        task = get_task(task_id)
        status = task["status"]
        if status == "success":
            return task
        if status == "fail":
            raise RuntimeError(f"task failed: {task}")
        time.sleep(poll_interval_s)
    raise TimeoutError(f"task {task_id} still {status} after {timeout_s}s")

task = wait_for_task(submit_task("a quiet coffee shop on a rainy afternoon"))
video_url = task["output"][0]["url"]
print(video_url)

A few important details:

  • status cycles queued → handling → archiving → success (or fail). Don't treat anything before success or fail as terminal.
  • Generation time scales with duration and current queue depth. A 5-second 720p job typically finishes in a few minutes; budget more for 1080p at 15s.
  • The output[].url lives in temporary storage. Download the bytes immediately and re-host them; do not store the URL itself and serve it to end users — it expires.
import urllib.request

def download(video_url: str, out_path: str) -> None:
    with urllib.request.urlopen(video_url) as resp, open(out_path, "wb") as f:
        f.write(resp.read())

download(video_url, "ocean.mp4")

Getting the result back: callback (production pattern)

Polling is fine for scripts and notebooks, but in a real backend you don't want a worker tied up for several minutes per video. Pass a callback object on submit, and hiapi will POST the finished task body to your URL exactly once.

curl -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": "an astronaut planting a flag on a glass beach",
      "aspect_ratio": "16:9",
      "resolution": "1080p",
      "duration": 6
    },
    "callback": {
      "url": "https://example.com/hooks/hiapi",
      "when": "final"
    }
  }'

Only when: "final" is currently supported — send any other value and you'll get:

{ "code": 400, "error_code": "INVALID_REQUEST",
  "message": "invalid callback.when: only 'final' is supported" }

Designing a robust receiver

Three rules keep the callback path safe:

  1. Idempotency. Persist by taskId. If you receive the same callback twice — and you will, eventually, after a retry — re-handling must be a no-op.
  2. Decouple ack from work. Return 200 OK from the webhook as soon as you've enqueued the payload. Do the actual download / re-upload / database write in a background job. Slow webhooks invite timeouts and re-deliveries.
  3. Fall back to polling. Store every taskId you submit in your own database with created_at. Run a sweeper that polls /v1/tasks/{id} for any row older than ~30 minutes with no terminal status yet. Callbacks are a fast path, not the only path.

A minimal FastAPI receiver:

from fastapi import FastAPI, Request

app = FastAPI()

@app.post("/hooks/hiapi")
async def hiapi_callback(req: Request):
    body = await req.json()
    task = body.get("data", body)  # callback wraps the same shape as GET
    task_id = task["taskId"]
    if task["status"] == "success":
        url = task["output"][0]["url"]
        # enqueue, don't download here
        await queue.put({"task_id": task_id, "url": url})
    return {"ok": True}

Errors you will actually hit

The error envelope is always the same shape — {code, error_code, message, data} — so you can build one handler. The ones worth special-casing for this model:

HTTPerror_codeTriggerFix
400INVALID_REQUESTUnknown field, bad enum value, duration outside 3–15Inspect message, it names the field — e.g. aspect_ratio: value must be one of '16:9'...
401—Missing or wrong key, or permission_deniedRe-check the Authorization: Bearer sk-... header
402—Balance can't cover the estimated costTop up at hiapi.ai/en/pricing, then retry
429—You're hammering the endpointBack off and retry with jitter

A real validation failure looks like this — copy it as your test fixture:

{
  "code": 400,
  "data": null,
  "error_code": "INVALID_REQUEST",
  "message": "invalid input: aspect_ratio: value must be one of '16:9', '9:16', '3:4', '4:3', '4:5', '5:4', '1:1', '9:21', '21:9'; resolution: value must be one of '720p', '1080p'; duration: maximum: got 99, want 15"
}

Notice how every field is reported in a single response. Validate locally where you can, but lean on this when you can't.

Related on hiapi

  • happyhorse-1.1/text-to-video model page — playground, full parameter UI, and live pricing
  • happyhorse-1.1/image-to-video — same model family, animates a still image instead of text
  • veo-3.1-fast/text-to-video and seedance-2.0 — alternative text-to-video models on the same /v1/tasks API
  • Unified Task API reference — the source of truth for POST /v1/tasks, GET /v1/tasks/:id, callbacks, and the error envelope
  • Browse all models and pricing

FAQ

Does happyhorse-1.1/text-to-video produce audio? Not via the task API — the request schema currently has no audio field, and sending one returns additional properties 'audio' not allowed. The output is a silent MP4. Layer your soundtrack downstream.

What aspect ratios are usable for vertical / short-form video? For TikTok and Reels-style output, 9:16 is the canonical choice. The API also accepts 4:5 (Instagram in-feed portrait) and 9:21 (ultra-tall) if you need something narrower.

What's the minimum and maximum duration? duration is an integer between 3 and 15 seconds, inclusive. Send 0 and you'll see duration: minimum: got 0, want 3; send 99 and you'll see duration: maximum: got 99, want 15. Pick the lowest value that captures the motion you need — duration is what you pay for.

Can I pass a seed for reproducible outputs? Not on this model right now. The request {"input": {"prompt": "...", "seed": 42}} fails with additional properties 'seed' not allowed. If reproducibility matters, log the full request body and retry with the same prompt; you'll get a similar — but not pixel-identical — result.

Is negative_prompt supported? No. Steer outputs by adding constraints to prompt itself (e.g. "...clean composition, no text, no watermarks").

How long do output URLs stay valid? The task's storage field is temp for the default plan, and the output[].url is a short-lived signed link. Download the file and re-host it the moment the task hits success. Don't store the original URL and serve it later.

How do I cap spend during development? Two cheap habits: keep duration: 3 until you've locked the prompt, and submit with resolution: "720p" until the storyboard is approved. Promote to 1080p and longer durations only for final renders.

Polling or callback — which should I use? Both, in that order. Start with polling because it's one fewer moving part. Move to callbacks the first time you have a backend that can't afford to block a worker on wait_for_task. Keep polling as the fallback sweeper either way; callbacks are best-effort, not guaranteed.

Why does my call return 402 even though I have credits? The API estimates the upper bound of the task cost — duration × per-second price — and reserves it up-front. If your balance can't cover that estimate, you get 402 immediately. Top up, retry, and only the actual cost is charged on success.

Latest models

View all models
  • GPT Image 2From $0.007/image
  • Nano Banana 2From $0.051/image
  • Seedream 5.0 ProFrom $0.050/image
  • Seedance 2.5From $0.121/s

Explore models

TextImageVideoAudio
Back to blog
GPT Image 2From $0.007/image
Nano Banana 2From $0.051/image
Seedream 5.0 ProFrom $0.050/image
Seedance 2.5From $0.121/s
View all models
TextChat and reasoning
ImageGenerate and edit
VideoText and image to video
AudioSpeech and music
Start generating
View model pricing
View all articles
How to Use the flux-3 API for Text-to-Video, Audio, and Continuation

How to Use the flux-3 API for Text-to-Video, Audio, and Continuation

minimax-music-3 API: curl & Python Guide

minimax-music-3 API: curl & Python Guide

How to use grok-imagine-image-2.0/image-to-image via the hiapi API: curl, Python, and a working request

How to use grok-imagine-image-2.0/image-to-image via the hiapi API: curl, Python, and a working request

How to Use grok-imagine-image-2.0/text-to-image via the hiapi API: curl, Python, and a Working Request

How to Use grok-imagine-image-2.0/text-to-image via the hiapi API: curl, Python, and a Working Request

How to Use the qwen-image-3.0 API: curl, Python, and a Working Request

How to Use the qwen-image-3.0 API: curl, Python, and a Working Request

How to Use qwen-image-3.0-pro via the hiapi API: curl, Python, and a Working Request

How to Use qwen-image-3.0-pro via the hiapi API: curl, Python, and a Working Request

Start generating