如何透過 API 生成 MiniMax H3 RH Enhanced 影片:文字、影格與多重參照,配合輪詢或 Webhook
How to Generate MiniMax H3 RH Enhanced Videos via API: Text, Frames and Multi-Reference, With Polling or a Webhook
教學示範如何透過 API 為 MiniMax H3 RH Enhanced 建立影片生成任務,並以輪詢或 Webhook 等待完成、下載及驗證 MP4。模型按請求欄位選擇文字生成、影格轉影片或多重參照模式,可生成 4–15 秒、帶音軌的 480P、768P 或 1080P 片段。作者的測試中,4 秒 1080P 片段耗時 218 秒;文章亦示範計費核對及常見請求錯誤。
A four-second 1080P clip from MiniMax H3 RH Enhanced took 218 seconds in our test. If your code holds an HTTP request open until the video exists, it will time out long before the video does. And because one model id covers three input modes, mixing the wrong fields gets you a 400 before anything renders.
This tutorial shows the full loop for MiniMax H3 RH Enhanced: create a task, wait for it with polling or a webhook, download the MP4, verify it, and confirm what you were billed. MiniMax H3 RH Enhanced is an enhanced build of MiniMax's open-weight H3 video model that generates 4-to-15-second clips with an audio track at 480P, 768P or 1080P, from text, from a first and/or last frame, or from up to 9 reference images and 3 reference videos, optionally driven by your own audio.
Disclosure: I work on apimodels.app, the API used in every example below, and the timings come from its test logs. I wrote this with AI assistance (DEV's "AI-Assisted" tier): the structure and prose were drafted with an AI, the request fields and error messages were checked against the API source, and the code is example code written against the documented contract.
What you need
- Python 3.10+ and
requests(pip install requests); Flask only if you want the webhook in Step 4. -
curland, for verification,ffprobefrom FFmpeg. - An API key in an environment variable:
export APIMODELS_KEY="sk_your_key_here". - Media as public HTTPS URLs. Images can also be sent as base64.
- For webhooks: a publicly reachable HTTPS URL. A tunnel such as ngrok works for local development;
localhostand private addresses are rejected.
Why one model id behaves like three
The mode is picked from the fields you send, in this order:
- Any of
reference_image_urls,reference_video_urlsorreference_video_audio_urls→ multi-reference. - Otherwise,
first_frame_url,last_frame_urlorreference_audio_urls(voice references) → image-to-video. - Otherwise → text-to-video, and
promptis required.
Frames and references live in different pipelines, so a request with both is rejected at create time rather than having half its inputs silently ignored. Every mode also accepts audio_url (a driving track for lip sync) and audio_mode.
The fields you will use most:
| Field | Required | Values |
|---|---|---|
model |
yes | minimax-h3-rh-enhanced |
prompt |
text-to-video | up to 20,000 characters |
duration |
yes | 4–15 (seconds; billed for this number) |
resolution |
no |
480p, 768p, 1080p (default 1080p) |
aspect_ratio |
no |
1:1 2:3 3:2 3:4 4:3 9:16 16:9 21:9
|
first_frame_url, last_frame_url
|
no | URL or base64 |
reference_image_urls |
no | up to 9 |
reference_video_urls |
no | up to 3 |
reference_video_audio_urls |
no | up to 2 (audio of the reference videos) |
audio_url |
no | driving audio for lip sync |
reference_audio_urls |
no | up to 3 voice references |
audio_mode |
no |
native (default), lock_source, remix_source, reference_only
|
callback_url |
no | public URL we POST the result to |
Step 1: Create a text-to-video task
Start at 480P and 4 seconds while you test; it is the fastest and smallest combination.
curl -s https://api.apimodels.app/v1/video/generations \
-H "Authorization: Bearer $APIMODELS_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "minimax-h3-rh-enhanced",
"prompt": "A potter shapes a clay bowl on a spinning wheel, she looks up and smiles at the camera, warm window light",
"duration": 4,
"resolution": "480p",
"aspect_ratio": "16:9"
}'
The response comes back immediately with a task ID and state: "pending":
{"code":200,"msg":"success","data":{"taskId":"<task_id>","state":"pending","model":"minimax-h3-rh-enhanced"}}
Step 2: Wait by polling
The status endpoint returns state as pending, processing, completed or failed. On completed you get resultUrls; on failed you get failMsg and a retryable flag. Poll every 10 to 15 seconds.
# h3e.py — example client
import json, os, time, requests
BASE = "https://api.apimodels.app/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['APIMODELS_KEY']}"}
def create(payload: dict) -> str:
r = requests.post(f"{BASE}/video/generations", headers=HEADERS, json=payload, timeout=60)
body = r.json()
if r.status_code != 200:
raise RuntimeError(f"{r.status_code}: {body.get('msg')}")
return body["data"]["taskId"]
def wait(task_id: str, every: int = 15, give_up_after: int = 1800) -> dict:
deadline = time.time() + give_up_after
while time.time() < deadline:
r = requests.get(f"{BASE}/video/generations", headers=HEADERS,
params={"task_id": task_id}, timeout=30)
data = r.json()["data"]
if data["state"] == "completed":
return data
if data["state"] == "failed":
raise RuntimeError(f"failed: {data.get('failMsg')} (retryable={data.get('retryable')})")
time.sleep(every)
raise TimeoutError(f"{task_id} still running after {give_up_after}s")
Step 3: Download the result
Result URLs point to MP4 files that are deleted after 30 days, so copy them to your own storage.
def download(url: str, path: str) -> None:
with requests.get(url, stream=True, timeout=300) as r:
r.raise_for_status()
with open(path, "wb") as f:
for chunk in r.iter_content(1 << 20):
f.write(chunk)
if __name__ == "__main__":
task_id = create({
"model": "minimax-h3-rh-enhanced",
"prompt": "A potter shapes a clay bowl on a spinning wheel, she looks up and smiles at the camera",
"duration": 4,
"resolution": "480p",
"aspect_ratio": "16:9",
})
print("task", task_id)
data = wait(task_id)
download(data["resultUrls"][0], f"{task_id}.mp4")
print("saved", f"{task_id}.mp4", "billed USD:", data["credits"])
Step 4: Or let the API call you
Add callback_url to the create body and the API POSTs to it when the task finishes, successful or not. The result URLs sit inside resultJson, which is a JSON string, so parse it. Return a 2xx quickly: each delivery attempt times out after 10 seconds and is tried up to three times.
# webhook.py — example receiver (Flask)
import json
from flask import Flask, request
app = Flask(__name__)
@app.post("/apimodels/callback")
def callback():
data = request.get_json(force=True)["data"]
urls = json.loads(data.get("resultJson") or "{}").get("resultUrls", [])
print(data["taskId"], data["state"], urls, data.get("credits"), data.get("failMsg"))
# enqueue the download here; don't do slow work inside the request
return "", 200
Keep polling as a backstop for the rare callback your server misses.
Step 5: Image-to-video with a driving audio track
Same client, different body. A first frame plus a voice line turns a still portrait into a speaking shot. In our end-to-end test this exact shape at 480P and 4 seconds finished in 50 seconds.
task_id = create({
"model": "minimax-h3-rh-enhanced",
"prompt": "She keeps working and talks to the camera with a warm smile",
"first_frame_url": "https://example.com/first.jpg",
"audio_url": "https://example.com/voice.mp3",
"duration": 6,
"resolution": "480p",
"aspect_ratio": "3:4",
})
Step 6: Multi-reference with your soundtrack locked
Reference images set who and where, a reference video sets the movement, and audio_mode: "lock_source" keeps your song untouched. This is the slowest shape: a 6.6-second reference video pushed a 4-second 480P test to 346 seconds, so it is a good place to use the webhook.
task_id = create({
"model": "minimax-h3-rh-enhanced",
"prompt": "The singer from image 1 performs on the stage from image 2, moving like the dancer in the reference video",
"reference_image_urls": ["https://example.com/singer.jpg", "https://example.com/stage.jpg"],
"reference_video_urls": ["https://example.com/dance.mp4"],
"audio_url": "https://example.com/song.mp3",
"audio_mode": "lock_source",
"duration": 10,
"resolution": "768p",
"aspect_ratio": "9:16",
"callback_url": "https://your-app.example.com/apimodels/callback",
})
Verify it works
Check the file's streams and length:
ffprobe -v error -show_entries stream=codec_type,width,height,r_frame_rate:format=duration \
-of default=noprint_wrappers=1 <task_id>.mp4
What our 2026-10-10 tests produced, so you know what "correct" looks like: a video stream at 24 fps plus an audio stream; 864×480 for 480P at 16:9, 544×768 for 480P at 3:4, 1920×1088 for 1080P at 16:9; and a duration about 0.46 seconds longer than requested.
Then confirm the charge for that one task:
curl -s https://api.apimodels.app/v1/records/<task_id> -H "Authorization: Bearer $APIMODELS_KEY"
data.settled is true once the amount is final and data.credits is the USD taken from your balance, for example 0.2 for 480P × 4 seconds before any account discount, and 0 for a failed task.
Where apimodels.app fits
apimodels.app is a multi-model API gateway: one API key and one OpenAI-compatible endpoint for more than 140 image, video, audio and language models. For most mainstream models it keeps more than one upstream route with automatic failover; failed requests are not charged; long jobs run asynchronously with the webhook shown above; and GET /v1/records/{id} returns the exact charge for any single request. This model does not have a failover route yet: a failed job is refunded, and resubmitting is up to you (check retryable).
Pricing for minimax-h3-rh-enhanced is per requested second: $0.05 at 480P, $0.07 at 768P and $0.10 at 1080P. Reference images, reference videos and audio inputs are free. The MiniMax H3 RH Enhanced API reference lists every field and alias.
Do not use it if you need a 1080P clip back in under a minute, need 2K or 4K, need clips longer than 15 seconds, or need a guaranteed native 1080P master: we have not verified whether 768P and 1080P are rendered natively or upscaled.
Common errors
All of these come back as HTTP 400 at create time. No task is created and nothing is charged.
-
duration is required (4-15 seconds): there is no default length. Sendduration. -
duration must be between 4 and 15 seconds (got "20"): split longer scenes into several clips. -
prompt is required for text-to-video (or pass first_frame_url / reference_image_urls): an empty prompt with no frames or references. -
first_frame_url / last_frame_url cannot be combined with reference_image_urls / reference_video_urls — use one mode per request: choose frames or references for each shot. -
resolution must be one of 480p / 768p / 1080p (got "720p"): there is no 720p tier. -
aspect_ratio must be one of 1:1 / 2:3 / 3:2 / 3:4 / 4:3 / 9:16 / 16:9 / 21:9 (got "5:4"). -
at most 9 reference images allowed (got 10): the same pattern applies to 3 videos, 3 voice references and 2 reference-video audio tracks. -
callback_url host "localhost" is not reachable from our servers …: use a public URL or a tunnel.
One failure arrives later, through failMsg: Content did not pass the safety review. Please change the reference media or prompt and try again. It is not charged.
Limits
- Resolution tops out at 1080P, and the 1080P file is 1920×1088, not 1920×1080. Crop if your pipeline is strict.
- Omitting
resolutionrenders at 1080P, the most expensive and slowest tier. - The image-to-video and multi-reference timings above come from a handful of our own tests.
- We have not measured lip-sync accuracy or voice similarity.
What turnaround time does your current video pipeline assume, and would a 218-second wait for a four-second 1080P clip break it?
I build apimodels.app, one API key for 140+ image, video, audio and language models.
來源:DEV Community · dev.to