minimax-h3-max turns one photo into a 5–15 second video, and its endpoint has no "camera angle," "multi-view," or "number of shots" parameter at all — the parameter reference lists prompt, first_frame_image, last_frame_image, duration, resolution, and aspect_ratio as its supported input fields. Put camera directions in prompt, which accepts up to 7,000 characters.
So a "multi-angle" clip — one shot cutting from a wide establishing view to a close-up to a tracking shot — isn't a parameter you set. It's a prompt you write. This recipe covers the request shape, a prompt pattern for requesting multiple camera angles inside one generated clip, and the production-shaped flow around it.
Generate with this prompt → Select 10 seconds and 768P. For image-to-video, add your frame image and omit aspect_ratio.
Prerequisites
Grab an API key from your hiapi dashboard. Every call below authenticates with Authorization: Bearer YOUR_API_KEY against the shared async task endpoint, POST https://api.hiapi.ai/v1/tasks.
Minimal example: one photo, three camera angles
first_frame_image anchors the subject and scene so the model has something concrete to cut away from and back to. The prompt describes each shot as an explicit, numbered cut. This motorcycle prompt is a code example, with no rendered sample attached.
A red vintage motorcycle parked on a wet city street at dusk, neon signs reflecting on the pavement. Shot 1 - low wide angle: the camera holds a static wide shot of the full motorcycle, rain lightly falling. Cut to Shot 2 - close-up: the camera pushes in tight on the chrome headlight and front wheel, tracking slowly right. Cut to Shot 3 - overhead top-down: the camera looks straight down at the motorcycle from above, slowly rotating. No text or subtitles.
Generate with this prompt → Add your own publicly accessible motorcycle image, select 10 seconds and 768P, and omit aspect_ratio.
import os
import requests
import time
API_KEY = os.environ["HIAPI_API_KEY"]
BASE = "https://api.hiapi.ai/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
payload = {
"model": "minimax-h3-max",
"input": {
"prompt": (
"A red vintage motorcycle parked on a wet city street at dusk, neon signs reflecting on the pavement. "
"Shot 1 - low wide angle: the camera holds a static wide shot of the full motorcycle, rain lightly falling. "
"Cut to Shot 2 - close-up: the camera pushes in tight on the chrome headlight and front wheel, tracking slowly right. "
"Cut to Shot 3 - overhead top-down: the camera looks straight down at the motorcycle from above, slowly rotating. "
"No text or subtitles."
),
"first_frame_image": "https://your-cdn.example.com/motorcycle-source.jpg",
"duration": 10,
"resolution": "768P",
},
}
# Frame images and aspect_ratio are mutually exclusive.
def response_data(response):
response.raise_for_status()
body = response.json()
if not isinstance(body, dict) or body.get("code") != 200:
raise ValueError("Expected a JSON object with code=200")
data = body.get("data")
if not isinstance(data, dict):
raise ValueError("Expected data to be an object")
return data
# Submit once. An uncertain POST response is not a reason to submit again.
try:
resp = requests.post(
f"{BASE}/tasks", headers=HEADERS, json=payload, timeout=(10, 60)
)
created = response_data(resp)
task_id = created.get("taskId")
if not isinstance(task_id, str) or not task_id.strip():
raise ValueError("Missing data.taskId")
except (requests.RequestException, ValueError) as exc:
raise RuntimeError(
"Could not confirm task creation. Check task records before resubmitting."
) from exc
print("taskId:", task_id, flush=True)
deadline = time.monotonic() + 600
while time.monotonic() < deadline:
try:
resp = requests.get(
f"{BASE}/tasks/{task_id}", headers=HEADERS, timeout=(10, 60)
)
task = response_data(resp)
except (requests.RequestException, ValueError) as exc:
raise RuntimeError(
f"Query failed; retain taskId={task_id} and resume GET for this ID."
) from exc
state = task.get("status")
if state == "fail":
raise RuntimeError(f"Task {task_id} failed: {task.get('error')}")
if state == "success":
outputs = task.get("output")
if not isinstance(outputs, list):
raise RuntimeError(f"Task {task_id}: missing output array")
video = next(
(item for item in outputs
if isinstance(item, dict) and item.get("type") == "video"
and isinstance(item.get("url"), str) and item["url"]),
None,
)
if video is None:
raise RuntimeError(f"Task {task_id}: no video URL in output")
print(video["url"])
print("expireAt (Unix seconds):", video.get("expireAt"))
break
if state not in {"queued", "handling", "archiving"}:
raise RuntimeError(f"Task {task_id}: unexpected status {state!r}")
time.sleep(min(3, max(0, deadline - time.monotonic())))
else:
raise TimeoutError(f"Polling timed out; continue GET with taskId={task_id}")
Swap first_frame_image for a real, publicly reachable HTTPS image URL, or drop it entirely to generate the same multi-angle sequence purely from text — minimax-h3-max handles both text-to-video and image-to-video through the same prompt field.
Writing the prompt: a numbered-shot pattern
Three choices make your intended shot sequence clear:
- One establishing sentence, then numbered shots. Describe the subject and setting once, then list each camera angle as its own labeled beat:
Shot 1 - [framing]: [action]. Cut to Shot 2 - [framing]: [action]."Cut to Shot N" explicitly requests a change of camera angle. - Use standard cinematography vocabulary.
low-angle wide shot,overhead top-down shot,close-up,slow dolly-in,side tracking shot,handheld— these name the framing and movement you want. Vague phrasing like "another cool angle" produces inconsistent results. - Budget shots against duration.
durationis an integer from 5 to 15 seconds. Two to three shots is realistic; cramming five distinct angles into a 5-second clip gives each one under a second, which tends to look like a glitch rather than a cut.
first_frame_image and last_frame_image are both optional and independent — set one, both, or neither. When you supply either, skip aspect_ratio: the fields are mutually exclusive, and the frame image determines the output's aspect ratio. Also note there's no guarantee of strict 3D or geometric consistency across angles — this is a generative video model, not a photogrammetry pipeline, so expect the subject's fine details to drift slightly between cuts even with a first-frame anchor.
Making it production-shaped
- Prefer a callback over polling once you're past the size of a script. Add
"callback": {"url": "https://your-domain.com/hiapi/callback", "when": "final"}to the request body and hiapi POSTs a notification when the task reaches a terminal state, instead of you pollingGET /v1/tasks/{taskId}in a loop. - Deduplicate by
taskId. Both polling responses and callbacks are keyed by the sametaskId— treat repeated terminal notifications for one ID as idempotent, not as separate jobs. - Download the output immediately. A successful task returns
data.output[].urlfor temporary storage. Select the output item whosetypeisvideoand check itsexpireAtUnix timestamp in seconds. Re-upload it to your own storage inside the same handler that processes the terminal state. - Handle both failure shapes. Bad request bodies (unsupported fields, out-of-range
duration, malformed image URLs) fail fast with400 INVALID_REQUESTat submission time. Auth problems return401witherror.code: "permission_denied". A task that fails during generation instead reports"status": "fail"on the polled/callback response — checkstatusbefore trustingoutput.
Related reading
- minimax-h3-max model page — live pricing, parameters, and an in-browser playground to test prompts before wiring up code.
- minimax-h3-max API docs — full parameter reference and callback details.
- H3 and H3 Max pricing — resolution rates and short-video budgets.
- hiapi pricing — current per-second rates for every model.
FAQ
Does minimax-h3-max have a dedicated multi-angle or multi-camera parameter?
No. The documented input fields are prompt, first_frame_image, last_frame_image, duration, resolution, and aspect_ratio. Describe multiple shots inside prompt rather than adding camera-angle fields.
Can I set aspect_ratio together with first_frame_image or last_frame_image?
No. aspect_ratio is mutually exclusive with either frame-image field. The supplied image determines the output ratio. Omit aspect_ratio whenever you pass either frame image.
How long can a multi-angle clip be?
duration accepts whole seconds from 5 to 15. Two to three distinct camera angles fit comfortably in that window; more than that tends to compress each shot to a fraction of a second.
Does the subject stay perfectly consistent across angles?
Not guaranteed. minimax-h3-max is a generative video model, so fine details (exact texture, minor proportions) can drift between cuts even when first_frame_image anchors the opening frame. Treat it as strong creative consistency, not pixel-exact multi-view reconstruction.
Can I generate a multi-angle clip from text only, with no source image?
Yes — omit first_frame_image and last_frame_image and describe the scene and shots in prompt. Text-only generation uses the default landscape canvas. For a portrait composition, supply a portrait frame image without aspect_ratio, or crop/pad the saved video in an editor.
What does an auth failure look like?
HTTP 401 with a body like {"error":{"code":"permission_denied","type":"hiapi_error","request_id":"..."}}. Double-check the Authorization: Bearer header and that the key hasn't been revoked from the dashboard.
Copy the motorcycle shot sequence and Generate with this prompt → Select 10 seconds and 768P; add your frame image to guide the opening composition.





