Search

Search HiAPI models, tools, and resources.

Back to blog
Tutorial

How to Use the minimax-h3-max API: curl, Python, and a Working Request

Text and frame-image requests, with curl, Python, callbacks, and idempotency.

hiapiminimax-h3-maxVideo APITutorialText to Video

minimax-h3-max generates 5–15 second videos from text or first/last-frame images at 480P or 768P. Start with the minimal request below, then add callbacks and idempotency for an application integration. Use this prompt → Select 6 seconds, 768P, and 16:9 manually.

What you'll build

A script that submits a text-to-video job to minimax-h3-max, waits for it to finish, and prints the resulting video URL for download. The examples below follow the current H3 Max API reference.

Prerequisite: an API key. Grab one from the hiapi dashboard and export it:

export HIAPI_API_KEY="sk-..."

Every request authenticates with Authorization: Bearer sk-....

The minimal request

minimax-h3-max runs on hiapi's unified async task endpoint. You create a task, then poll (or get a callback) until it finishes.

curl

a red panda sipping tea on a rainy Tokyo balcony, cinematic lighting
curl --fail-with-body --connect-timeout 10 --max-time 60 -X POST "https://api.hiapi.ai/v1/tasks" \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "minimax-h3-max",
    "input": {
      "prompt": "a red panda sipping tea on a rainy Tokyo balcony, cinematic lighting",
      "duration": 6,
      "resolution": "768P",
      "aspect_ratio": "16:9"
    }
  }'

A successful submission returns data.taskId; save it before polling. This is the response shape:

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

If submission times out or its response is lost, check task history before another POST. Once you have the ID, keep querying that task until success or fail:

curl --fail-with-body --connect-timeout 10 --max-time 60 "https://api.hiapi.ai/v1/tasks/tk-hiapi-..." \
  -H "Authorization: Bearer $HIAPI_API_KEY"

When data.status is success, select the entry with type: "video" in data.output and read its url. Download it before that entry's expireAt deadline (Unix seconds). Stop on fail and inspect data.error.

Use this prompt → For this text-only example, select 6 seconds and 768P.

Input fields that actually work

Use the current documented input fields:

FieldTypeNotes
promptstringrequired; up to 7,000 characters
durationinteger5–15 whole seconds; default 5
resolutionenum"480P" | "768P"; default "768P"
aspect_ratioenum"21:9", "16:9", "4:3", "1:1", "3:4", "9:16"
first_frame_imagestringpublic image URL for the opening frame
last_frame_imagestringpublic image URL for the ending frame; can also be used alone

Frame images and aspect_ratio are mutually exclusive. To guide the opening, ending, or both, replace aspect_ratio with your public frame URLs:

{
  "model": "minimax-h3-max",
  "input": {
    "prompt": "a red panda sipping tea on a rainy Tokyo balcony, cinematic lighting",
    "duration": 6,
    "resolution": "768P",
    "first_frame_image": "https://your-domain.com/images/opening.jpg",
    "last_frame_image": "https://your-domain.com/images/ending.jpg"
  }
}

Replace the image URL placeholders with accessible files. Text-only generation uses the default landscape canvas, including when 9:16 is supplied. For portrait output, use a frame image with the desired ratio or crop/pad in post-production.

Python

import os
import time
import urllib.request
import urllib.error
import json

API_KEY = os.environ["HIAPI_API_KEY"]
BASE = "https://api.hiapi.ai/v1/tasks"
HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}


def request(method, url, body=None):
    data = json.dumps(body).encode() if body is not None else None
    req = urllib.request.Request(url, data=data, headers=HEADERS, method=method)
    with urllib.request.urlopen(req, timeout=30) as response:
        result = json.load(response)
    if not isinstance(result, dict) or result.get("code") != 200:
        raise ValueError(f"Unexpected API response: {result!r}")
    if not isinstance(result.get("data"), dict):
        raise ValueError("Missing data object")
    return result["data"]


payload = {
  "model": "minimax-h3-max",
  "input": {
    "prompt": "a red panda sipping tea on a rainy Tokyo balcony, cinematic lighting",
    "duration": 6,
    "resolution": "768P",
    "aspect_ratio": "16:9"
  }
}
try:
    created = request("POST", BASE, payload)
    task_id = created.get("taskId")
    if not isinstance(task_id, str) or not task_id.strip():
        raise ValueError("Missing taskId")
except (OSError, ValueError) as exc:
    raise SystemExit(
        f"Submission outcome is unconfirmed: {exc}. "
        "Check task history before another POST; do not blindly resubmit."
    )

print("taskId:", task_id, flush=True)
deadline = time.monotonic() + 600
while time.monotonic() < deadline:
    try:
        task = request("GET", f"{BASE}/{task_id}")
    except urllib.error.HTTPError as exc:
        if exc.code in (400, 401, 403, 404):
            raise SystemExit(
                f"Query rejected ({exc.code}). Correct URL/auth, then GET existing task {task_id}."
            )
        print(f"Query error: {exc}; keep querying task {task_id}", flush=True)
        time.sleep(5)
        continue
    except (OSError, ValueError) as exc:
        print(f"Query error: {exc}; keep querying task {task_id}", flush=True)
        time.sleep(5)
        continue
    status = task.get("status")
    if status not in ("queued", "handling", "archiving", "success", "fail"):
        raise RuntimeError(f"Unexpected task status {status!r}; keep task ID {task_id}")
    if status == "success":
        output = task.get("output")
        if not isinstance(output, list):
            raise RuntimeError(f"Invalid output; keep task ID {task_id}")
        videos = [item["url"] for item in output
                  if isinstance(item, dict) and item.get("type") == "video"
                  and isinstance(item.get("url"), str) and item["url"]]
        if not videos:
            raise RuntimeError(f"No video URL; keep task ID {task_id}")
        print(videos[0])
        break
    if status == "fail":
        raise RuntimeError(f"Task {task_id} failed: {task!r}")
    time.sleep(5)
else:
    raise TimeoutError(f"Keep querying existing task {task_id}; do not POST again")

Production patterns

A one-off script can poll in a loop. A production integration needs three more things.

Use a callback instead of polling. Add a callback object to the create-task request and hiapi will POST to it once the task reaches a terminal state — no polling loop, no wasted requests:

{
  "model": "minimax-h3-max",
  "callback": {"url": "https://your-app.example.com/hiapi/callback", "when": "final"},
  "input": {"prompt": "...", "duration": 6, "resolution": "768P", "aspect_ratio": "16:9"}
}

when currently only supports "final" (fires on both success and failure) and defaults to it if omitted. Polling is simpler for a script you run by hand; a callback is the right call once you're submitting more than a handful of jobs per minute, because callbacks reduce query traffic and simplify notification handling. Polling the existing task does not create another generation charge.

Set an Idempotency-Key header. The create-task endpoint accepts an optional Idempotency-Key header (up to 255 bytes). Retrying a request with the same key under the same account creates the task only once — a replay returns the original taskId instead of billing a second render. Use this any time a request might be retried after a timeout, so a network hiccup doesn't turn into a duplicate charge:

curl --fail-with-body --connect-timeout 10 --max-time 60 -X POST "https://api.hiapi.ai/v1/tasks" \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Idempotency-Key: order-42-video-render" \
  -H "Content-Type: application/json" \
  -d '{"model": "minimax-h3-max", "input": {"prompt": "...", "duration": 6, "resolution": "768P", "aspect_ratio": "16:9"}}'

Handle the two real error shapes. An invalid or missing API key returns HTTP 401:

{"error": {"code": "permission_denied", "message": "...", "request_id": "...", "type": "hiapi_error"}}

An invalid field (bad enum value, wrong type, out-of-range duration) returns HTTP 400:

{"code": 400, "data": null, "error_code": "INVALID_REQUEST", "message": "..."}

Branch on these explicitly — permission_denied means fix your key, INVALID_REQUEST means fix your payload, and neither is worth retrying without a change.

FAQ

Is minimax-h3-max a chat or language model? It is a video model: supply a prompt, optionally guide the opening or ending with images, and receive a video output.

Does minimax-h3-max support image-to-video? Yes. Use first_frame_image, last_frame_image, or both alongside prompt. Omit aspect_ratio whenever you supply a frame image.

What resolutions and aspect ratios are supported? resolution is "480P" or "768P". The schema lists 21:9, 16:9, 4:3, 1:1, 3:4, and 9:16, but text-only generation uses the default landscape canvas. Frame images determine the ratio when supplied; omit aspect_ratio in that path.

How much does minimax-h3-max cost? HiAPI charges $0.072 per output second at 480P or $0.115 at 768P. A 6-second clip has a budget of $0.432 or $0.69, respectively. See the H3 and H3 Max pricing guide for batch budgets.

Should I poll or use a callback? Poll for scripts and low-volume testing. Switch to callback.url once you're generating enough clips that a polling loop would waste requests or add latency to your own workflow.

Why did I get a 401 with permission_denied? Your Authorization: Bearer sk-... header is missing, malformed, or the key is invalid/revoked. Regenerate a key from the dashboard and confirm the header is set exactly as shown above.

Why did I get a 400 with INVALID_REQUEST? One of your input fields doesn't match the schema — usually an out-of-range duration or a resolution/aspect_ratio value outside the enum. The error message names the offending field.

Can I use this from Node.js instead of Python or curl? Yes — it's the same POST /v1/tasks / GET /v1/tasks/{taskId} pair with any HTTP client; only the request-building syntax changes.

Copy the red-panda example and Use this prompt →. For more scene ideas, see four H3 and H3 Max prompt examples.

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