Search

Search HiAPI models, tools, and resources.

Back to blog
Tutorial

How to Use the Ray 3.2 Text-to-Video API: curl, Python, and a Working Request

hiapitutorialvideorecipe

Ray 3.2 is a text-to-video model in the hiapi catalog, and you call it through the same unified async task API as every other video model — model id ray-3.2/text-to-video, submitted to POST /v1/tasks. Unlike some video models whose only knob is the prompt, Ray 3.2 gives you three real output controls — duration, resolution, and aspect_ratio — each strictly validated server-side.

This guide gets you one request that works on the first try: the exact input schema (verified against the live API on 2026-10-19), a copy-paste curl call, a complete Python submit/poll/download script, and the production details — callbacks, idempotent retries, and the error responses you'll actually see.

What you'll build, and what you need

Goal: submit a text prompt, poll the task, and download a silent .mp4 clip (5 or 10 seconds).

Prerequisites:

  • A hiapi API key — grab one from your dashboard. Keys look like sk-... and go in the Authorization: Bearer header. New to the task API? Start with the quickstart.
  • Any HTTP client. We'll show curl and Python (requests).

Ray 3.2 output is billed per clip, and the price scales hard with your choices — see pricing for current rates. Start at 540p / 5 seconds while you iterate on prompts; a single 1080p 10-second clip can cost many times a budget-sized image render, so don't leave your dev loop running at max settings.

The exact input schema (one required field, three strict enums)

Everything goes inside an input object. Verified field-by-field against the live API:

FieldTypeRequiredAllowed valuesDefault
promptstringyesScene description, up to 6000 characters—
durationintegerno5 or 105
resolutionstringno540p, 720p, 1080p540p
aspect_ratiostringno16:9, 9:16, 1:1, 4:3, 3:4, 21:916:9

Three things worth internalizing before you write code:

  1. duration is an integer, not a string. Send the bare number — "duration": 5. This is the opposite of some other task-API video models (hailuo-2.3, for example, wants "duration": "6" as a string). If you're porting a request body across models, re-check this field; it's the single most common cross-model mistake.
  2. The enums are validated, not clamped. An out-of-range value is rejected with a 400, not silently rounded. There's no 2k, no 480p, no 2:1.
  3. There's no prompt_optimizer, seed, or negative_prompt. Framing is controlled entirely through aspect_ratio plus the prompt. If you send a field the schema doesn't know, expect a 400 — input schemas on /v1/tasks vary per model, so don't assume a key that worked elsewhere carries over.

What the validation actually returns

These are real responses from the live endpoint (invalid values submitted deliberately, no task created, nothing billed):

// POST with "resolution": "2k"
{"code":400,"data":null,"error_code":"INVALID_REQUEST",
 "message":"invalid input: resolution: value must be one of '540p', '720p', '1080p'"}

// POST with "duration": 7
{"code":400,"data":null,"error_code":"INVALID_REQUEST",
 "message":"invalid input: duration: value must be one of 5, 10"}

// POST with "aspect_ratio": "2:1"
{"code":400,"data":null,"error_code":"INVALID_REQUEST",
 "message":"invalid input: aspect_ratio: value must be one of '16:9', '9:16', '1:1', '4:3', '3:4', '21:9'"}

The message names the exact field and the exact allowed set — so if you ever forget an enum, the API tells you. Note the unquoted 5, 10 for duration (numbers) versus the quoted '540p' strings for resolution: that quoting is your reminder of which field is an integer.

Minimal working example: curl

Create the task:

curl -s -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer sk-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "ray-3.2/text-to-video",
    "input": {
      "prompt": "A street dancer spins under neon signs on a rainy night, puddles reflecting pink and blue light, her jacket swirling with the turn, the camera circling low around her, cinematic",
      "duration": 5,
      "resolution": "540p",
      "aspect_ratio": "9:16"
    }
  }'

The create response carries a task id (trimmed):

{ "data": { "taskId": "task_01k..." } }

Poll it until it reaches a terminal state:

curl -s https://api.hiapi.ai/v1/tasks/task_01k... \
  -H "Authorization: Bearer sk-YOUR_KEY"

While the job runs, data.status reports an in-progress state. The two terminal states are success and fail. On success (trimmed):

{
  "data": {
    "taskId": "task_01k...",
    "status": "success",
    "output": [ { "url": "https://.../clip.mp4" } ]
  }
}

Download data.output[0].url immediately. Output URLs are signed and expire — persist the bytes to your own storage rather than hot-linking the task URL. Ray 3.2 returns a silent MP4 (no audio track), so don't wait for an audio stream that never comes.

Complete Python script (submit → poll → download)

import time
import requests

API_BASE = "https://api.hiapi.ai/v1"
API_KEY = "sk-YOUR_KEY"
HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}


def create_task(prompt: str, duration: int = 5,
                resolution: str = "540p", aspect_ratio: str = "9:16") -> str:
    resp = requests.post(
        f"{API_BASE}/tasks",
        headers=HEADERS,
        json={
            "model": "ray-3.2/text-to-video",
            "input": {
                "prompt": prompt,
                "duration": duration,          # 5 or 10 — an INTEGER, not a string
                "resolution": resolution,      # "540p" | "720p" | "1080p"
                "aspect_ratio": aspect_ratio,  # "16:9" | "9:16" | "1:1" | "4:3" | "3:4" | "21:9"
            },
        },
        timeout=30,
    )
    resp.raise_for_status()
    return resp.json()["data"]["taskId"]


def wait_task(task_id: str, timeout_s: int = 900, poll_interval: int = 5) -> dict:
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        resp = requests.get(f"{API_BASE}/tasks/{task_id}", headers=HEADERS, timeout=30)
        resp.raise_for_status()
        task = resp.json()["data"]
        if task["status"] == "success":
            return task
        if task["status"] == "fail":
            err = task.get("error") or {}
            raise RuntimeError(f"task failed: {err.get('code')}: {err.get('message')}")
        time.sleep(poll_interval)  # any non-terminal status = still running
    raise TimeoutError(f"task {task_id} not finished after {timeout_s}s")


task_id = create_task(
    "A paper boat drifting down a rain-filled gutter, droplets rippling the surface, "
    "autumn leaves swirling past, shot low to the water, soft overcast light"
)
print("task created:", task_id)

task = wait_task(task_id)
video_url = task["output"][0]["url"]

# Output URLs expire — download right away and store the bytes yourself.
video = requests.get(video_url, timeout=120).content
with open("ray32-clip.mp4", "wb") as f:
    f.write(video)
print(f"saved ray32-clip.mp4 ({len(video) / 1e6:.1f} MB)")

Run it with pip install requests, swap in your key, and you'll have an .mp4 on disk when the task completes. Video tasks take minutes, not seconds — give your poll loop a generous deadline (15 minutes above) instead of assuming a fast return.

Production notes: callbacks, idempotency, errors

Prefer a callback over polling

For anything beyond a script, register a webhook at create time instead of polling:

{
  "model": "ray-3.2/text-to-video",
  "input": { "prompt": "...", "duration": 10, "resolution": "720p" },
  "callback": { "url": "https://your-app.example.com/hooks/hiapi", "when": "final" }
}

With "when": "final" hiapi calls your endpoint once, when the task reaches a terminal state — no poll traffic, no completions missed between intervals. Your handler then does a GET /v1/tasks/<id> (or reads the delivered body) to fetch output[0].url. Polling is still fine for CLIs and notebooks that can't expose an HTTPS endpoint.

Make retries idempotent

Persist the taskId the moment the create call returns. If your process crashes mid-generation, resume by polling the stored id — don't re-create the task, or you'll pay for a second render. Only re-create when the original create request itself failed before returning a task id. Because Ray 3.2 bills per clip and clips aren't cheap at higher resolution, a duplicate create is a real cost mistake, not just wasted time.

The errors you'll actually see

Every row below is a verified live response shape:

SymptomResponseFix
Missing/invalid key401, nested {"error":{"code":"permission_denied","type":"hiapi_error","request_id":"..."}}Check the Authorization: Bearer sk-... header and that the key has access. Quote the request_id if you contact support.
Wrong enum value400 INVALID_REQUEST, flat {"code":400,"error_code":"INVALID_REQUEST","message":"invalid input: <field>: value must be one of ..."}Read the field named in message; it lists the exact allowed set.
duration sent as a string400 INVALID_REQUESTSend the bare integer 5 or 10, not "5".
Unknown model id400, error_code: "MODEL_UNAVAILABLE", "model not available via /v1/tasks"Use ray-3.2/text-to-video exactly — check the model page for the canonical id.
Bad task id on poll404, flat {"code":404,"data":null,"message":"task not found"} (note: no error_code on 404)Wrong id, or you're querying with a different account's key.
status: "fail" while pollingterminal task state, detail in data.errorRead data.error.code / data.error.message. Failed tasks are refunded; content-policy flags and upstream capacity issues surface here — retry with a reworded prompt for a content flag.

FAQ

What's the model id, and which endpoint do I call? ray-3.2/text-to-video, submitted to POST https://api.hiapi.ai/v1/tasks as the top-level model with parameters under input. It's asynchronous — you get a taskId back and poll GET /v1/tasks/<id> (or register a callback).

Is duration in seconds, and why does "5" fail? Clips are 5 or 10 seconds, and the field is an integer: send 5 or 10. A quoted string, or any other number like 7, fails with 400 INVALID_REQUEST: duration: value must be one of 5, 10.

Can I set resolution and aspect ratio? Yes — that's where Ray 3.2 differs from prompt-only video models. resolution is 540p, 720p, or 1080p; aspect_ratio is one of 16:9, 9:16, 1:1, 4:3, 3:4, 21:9. Both are optional (defaults 540p and 16:9) and both reject out-of-range values with a 400.

How much does one clip cost? Ray 3.2 is billed per clip, and resolution is the biggest lever — a 1080p clip costs several times a 540p one at the same length, and a 10-second clip is roughly triple its 5-second counterpart. Rates change, so check the pricing page rather than hardcoding a number, and prototype at 540p/5.

Does the clip have audio? No — Ray 3.2 text-to-video returns a silent MP4. Add sound in post or with a separate audio model if you need it.

Does Ray 3.2 support image-to-video? Yes, as a separate model id on the same task API: ray-3.2/image-to-video. Its input schema differs (it takes a reference image), so probe it before reusing this request body — see the image-to-video model page.

My generation is slow or seems stuck. What's normal? Video tasks run for minutes. Keep a non-terminal data.status in your poll loop and wait — budget a 10–15 minute deadline, or use a callback with "when": "final" so hiapi notifies you at the terminal state instead of you guessing an interval.

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