この記事では、BeatAPI 経由で MiniMax H3 Normal の動画タスクを1本送り、状態を確認して、完成した動画を保存するまでの最小構成をまとめます。
動画生成 API でよくある失敗は、「送信が成功した」と「動画ができた」を同じものとして扱うことです。MiniMax H3 Normal も非同期 API なので、送信した瞬間に動画は返ってきません。タスクIDを先に保存し、終わるまで確認する流れを最初に作っておくと、タイムアウトのたびに同じ動画をもう1本作ってしまう事故を防げます。
前提
- BeatAPI の API キー(サーバー側の環境変数
BEATAPI_API_KEYに置く。ブラウザの JavaScript には入れない) - curl、Node.js 18 以上
- 料金は 2026年11月5日時点の beatapi.io の表示価格です。まとめて生成する前に料金ページで再確認してください。
料金(8秒・1回あたり)
| 解像度 | 料金 |
|---|---|
| 480p | $0.14 |
| 768p | $0.16 |
| 1080p | $0.20 |
モデルIDは minimax-h3-normal で、指定できる解像度は 480p / 768p / 1080p の3つです。2K や 4K はありません。何も指定しないと 768p になります。
おすすめは「下書きは 480p で回し、採用するカットだけ 1080p で作り直す」です。
1. タスクを送信して ID を保存する
curl --fail-with-body -X POST https://api.beatapi.io/v1/videos/tasks \
-H "Authorization: Bearer $BEATAPI_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: product-0001-h3n-draft-v1" \
-d '{
"model": "minimax-h3-normal",
"prompt": "A white serum bottle on a wet black stone. Soft side light, slow push-in, no text overlay.",
"duration": 8,
"aspect_ratio": "16:9",
"resolution": "480p"
}'
指定できる値(公開 OpenAPI の MinimaxH3NormalVideoRequest より):
| パラメータ | 値 |
|---|---|
duration |
5〜15 の整数(秒)。省略すると 5 |
aspect_ratio |
16:9 / 9:16 / 1:1 / 4:3 / 3:4 / 21:9
|
resolution |
480p / 768p / 1080p(小文字の p) |
images |
開始フレーム1枚、または開始・終了フレーム2枚 |
reference_images / reference_videos
|
参照入力。images とは同時に使えない |
音声のオン・オフを切り替えるパラメータはありません。生成される動画には音声が付きます。尺を省略すると 5秒になるので、8秒で見積もるなら duration: 8 を明示します。
作成に成功すると、data にタスクが返ります。
{
"data": {
"id": "task_8K2qA",
"object": "task",
"task_kind": "video",
"model": "minimax-h3-normal",
"status": "queued",
"output": null,
"request_id": "req_abc123",
"error_code": null,
"error_message": null
}
}
ここで大事なのは2つです。
-
data.idは、画面を更新するより先に保存する。自社のジョブID、Idempotency-Key、送った設定も一緒に残す。 - 応答を受け取る前に通信が切れたら、同じ本文・同じ
Idempotency-Keyで再送する。キーを変えると別のタスクになり、同じ動画を二重に作ってしまうことがあります。
2. 状態を確認する
curl --fail-with-body https://api.beatapi.io/v1/tasks/task_8K2qA \
-H "Authorization: Bearer $BEATAPI_API_KEY"
status は queued → processing → succeeded または failed と進みます。前の2つの間は待ち、後ろの2つで止めます。
Node.js で、送信から保存までを1ファイルにまとめるとこうなります。
// h3n.ts — 実行例: npx tsx h3n.ts
import { writeFile } from "node:fs/promises";
const baseUrl = "https://api.beatapi.io";
const headers = {
Authorization: `Bearer ${process.env.BEATAPI_API_KEY}`,
"Content-Type": "application/json",
};
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
async function submit(prompt: string, key: string): Promise<string> {
const res = await fetch(`${baseUrl}/v1/videos/tasks`, {
method: "POST",
headers: { ...headers, "Idempotency-Key": key },
body: JSON.stringify({
model: "minimax-h3-normal",
prompt,
duration: 8,
aspect_ratio: "16:9",
resolution: "480p",
}),
});
if (!res.ok) throw new Error(`submit failed: ${res.status} ${await res.text()}`);
const { data } = await res.json();
return data.id; // まず保存する
}
async function waitForTask(taskId: string) {
let delayMs = 5_000;
const deadline = Date.now() + 12 * 60_000;
while (Date.now() < deadline) {
const res = await fetch(`${baseUrl}/v1/tasks/${taskId}`, { headers });
if (res.status === 429) {
// Retry-After を秒で読む。読めなければ 10 秒待つ
const ra = Number(res.headers.get("retry-after"));
await sleep((Number.isFinite(ra) && ra > 0 ? ra : 10) * 1_000);
continue;
}
if (res.ok) {
const { data } = await res.json();
if (data.status === "succeeded" || data.status === "failed") return data;
} else if (res.status < 500) {
throw new Error(`task query failed: ${res.status}`);
}
await sleep(delayMs + Math.floor(Math.random() * 1_000));
delayMs = Math.min(Math.round(delayMs * 1.35), 15_000);
}
// 待つのをやめるだけ。タスクIDは残して、あとで照合する
throw new Error(`client deadline reached; keep the task id: ${taskId}`);
}
async function main() {
const id = await submit(
"A white serum bottle on a wet black stone. Soft side light, slow push-in, no text overlay.",
"product-0001-h3n-draft-v1",
);
console.log("task id:", id);
const task = await waitForTask(id);
if (task.status === "failed") {
console.error("failed:", task.error_code, task.error_message);
return;
}
const media = task.output?.media?.find((m: any) => m.type === "video");
if (!media?.url) throw new Error("no video in output");
const file = await fetch(media.url);
if (!file.ok) throw new Error(`download failed: ${file.status}`);
await writeFile(`${id}.mp4`, Buffer.from(await file.arrayBuffer()));
console.log("saved:", `${id}.mp4`);
}
main().catch((e) => {
console.error(e);
process.exit(1);
});
期限に達しても、それは「こちらが待つのをやめた」というだけで、タスクが失敗したわけではありません。新しく送り直す前に、保存したIDでもう一度確認してください。
3. 動画を保存する
成功すると data.output.media[] に動画が入ります。
{
"data": {
"id": "task_8K2qA",
"status": "succeeded",
"output": {
"media": [
{
"type": "video",
"url": "https://media.beatapi.io/outputs/task_8K2qA/video.mp4",
"mime_type": "video/mp4"
}
]
}
}
}
type: "video"、mime_type、URL に実際に届くかを確認してから「完成」にします。URL は永久保存ではないので、長く使う動画は自社のストレージにコピーし、タスクIDと紐づけておきます。
4. 予算の目安
商品動画を1本採用するまでの例です(いずれも8秒)。
| 工程 | 設定 | 本数 | 金額 |
|---|---|---|---|
| 下書き |
minimax-h3-normal・480p |
5本 | $0.70 |
| 仕上げ |
minimax-h3-normal・1080p |
1本 | $0.20 |
| 合計 | $0.90 |
これは生成費の試算です。採用までに何本かかるかはプロンプトと用途によって変わるので、最初の数商品で実際の本数を記録してから見積もりを固めます。
5. うまくいかないとき
| 現象 | やること |
|---|---|
queued のまま長い |
同じIDで確認を続ける。代わりのタスクを送らない |
| こちらの通信がタイムアウト | 新規送信の前に、保存したIDを確認する |
| ID を受け取る前に切れた | 同じ本文・同じ Idempotency-Key で再送する |
400 が返る |
resolution の表記(720p は不可、768p)と images / reference_* の併用を確認する |
failed で終わった |
error_code と error_message を保存し、入力を直すか判断する |
まとめ
- モデルIDは
minimax-h3-normal、解像度は480p/768p/1080p -
durationは省略すると5秒。8秒で見積もるなら明示する - 「IDを保存 → 状態確認 → 出力を検証して保存」の順を崩さない
出典・確認日
- BeatAPI 公開 API ドキュメント(openapi.json の MinimaxH3NormalVideoRequest、タスク取得):2026年11月5日確認
- 料金:api.beatapi.io/v1/pricing と beatapi.io 料金ページ(8秒):2026年11月5日確認
- 記事中のレスポンス例は公開スキーマに沿った例で、実際の請求額と照合したものではありません
モデルの仕様と料金:https://beatapi.io/minimax-h3-api?variant=minimax-h3-normal
