Makernice APIドキュメント:クイックスタート
- おすすめの用途
- 自分のコードからRESTで動画・画像生成を呼び出す開発者向けです。見積もり、作成のあと、ポーリングまたはWebhookで結果を受け取ります。
- 向かない用途
- Makernice APIは、コードを書かずに動画や画像を作るためのものではありません。その場合は、同じモデル・料金・残高を使えるコンソールの「作成」をご利用ください。
APIキー1つとリクエスト3回で動画を生成できます。見積もりを取得し、生成を作成し、完了するまでポーリングします。
1. APIキーを作成する
コンソールで開発者 → APIキーを開き、APIキーを作成します。すべてのリクエストにBearerトークンとして付けて送ってください。生成の料金はウォレットから差し引かれます。APIキーごとに利用上限を設定できます。
2. 見積もりを取得する
curl https://api.makernice.com/v1/generations/quote \
-H "Authorization: Bearer $MAKERNICE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"seedance-2.5","mode":"t2v","prompt":"温室の中をゆっくり進むドリーショット","duration":5,"resolution":"720p"}'
# → (値は説明用){ "id": "qt_…", "amount": "500000", "expires_at": "…", "facts": { "quantity": 5, … } }金額は米ドル建てで、単位は100万分の1ドル(micro-USD)です。1,000,000 = $1.00。見積もりは料金を12分間固定します。
3. 生成を作成する
curl https://api.makernice.com/v1/generations \
-H "Authorization: Bearer $MAKERNICE_API_KEY" \
-H "Idempotency-Key: 7c1e9a2b-order-1842" \
-H "Content-Type: application/json" \
-d '{"quote_id":"qt_…","input":{"model":"seedance-2.5","mode":"t2v","prompt":"温室の中をゆっくり進むドリーショット","duration":5,"resolution":"720p"}}'
# → 202 { "id": "gen_…", "status": "queued", "billing": "held", … }見積もり額が残高から確保されます。生成が失敗した場合や、待機中にキャンセルした場合、確保は全額解除されます。同じIdempotency-Keyで再試行すると、二重に課金されることなく同じ生成が返ります。
4. 完了するまでポーリングする
curl https://api.makernice.com/v1/generations/gen_… \
-H "Authorization: Bearer $MAKERNICE_API_KEY"
# status: queued → generating → moderating → succeeded | failed | canceled
# 成功したら: outputs[0].url に APIキー付きで GET すると、ファイルへの 302 が返ります5. ファイルをダウンロードする
curl -L -o video.mp4 https://api.makernice.com/v1/generations/gen_…/outputs/0 \
-H "Authorization: Bearer $MAKERNICE_API_KEY"Python:見積もり、作成、ポーリング
MAKERNICE_API_KEYを設定してから実行します。
import os
import time
import uuid
from decimal import Decimal
import requests
API = "https://api.makernice.com/v1"
ORIGIN = "https://api.makernice.com"
HEADERS = {"Authorization": f"Bearer {os.environ['MAKERNICE_API_KEY']}"}
TIMEOUT_SECONDS = 30
POLL_INTERVAL_SECONDS = 5
MAX_WAIT_SECONDS = 30 * 60
FINAL = {"succeeded", "failed", "canceled"}
def call(method, path, headers=None, **kwargs):
# Waits out 429 (rate_limited, concurrency_limit_reached) using Retry-After; raises on any other error.
for _ in range(5):
r = requests.request(method, API + path, headers={**HEADERS, **(headers or {})}, timeout=TIMEOUT_SECONDS, **kwargs)
if r.status_code == 429:
time.sleep(int(r.headers.get("Retry-After", "5")))
continue
if not r.ok:
raise RuntimeError(f"{method} {path} -> {r.status_code}: {r.text}")
return r.json()
raise RuntimeError(f"{method} {path}: still rate limited")
job = {
"model": "seedance-2.5",
"mode": "t2v",
"prompt": "A slow dolly shot through a greenhouse",
"duration": 5,
"resolution": "720p",
}
# 1. Quote: the exact amount, locked for 12 minutes
quote = call("POST", "/generations/quote", json=job)
usd = Decimal(quote["amount"]).scaleb(-6) # micro-USD integer string -> dollars, no float rounding
print(f"Quoted ${usd} (price list v{quote['price_version']}), valid until {quote['expires_at']}")
# 2. Create: same input + quote_id. A retry with the same Idempotency-Key returns the same generation
idempotency_key = str(uuid.uuid4())
gen = call("POST", "/generations", json={"quote_id": quote["id"], "input": job}, headers={"Idempotency-Key": idempotency_key})
# 3. Poll: queued -> generating -> moderating -> succeeded | failed | canceled (checking: keep polling)
deadline = time.monotonic() + MAX_WAIT_SECONDS
while gen["status"] not in FINAL:
if time.monotonic() > deadline:
raise SystemExit(f"{gen['id']} is still {gen['status']}; poll it again later")
time.sleep(POLL_INTERVAL_SECONDS)
gen = call("GET", f"/generations/{gen['id']}")
if gen["status"] != "succeeded":
# billing is "released": the hold went back to your balance
raise SystemExit(f"{gen['status']}, billing {gen['billing']}: {gen['error']}")
# 4. Download: outputs[].url is relative to the API origin and redirects (302) to the file
output = gen["outputs"][0]
if output["state"] == "blocked":
raise SystemExit("Output blocked by moderation (the run is charged); contact support for a manual review")
r = requests.get(ORIGIN + output["url"], headers=HEADERS, timeout=120) # requests drops Authorization on the redirect
r.raise_for_status()
with open("output.mp4", "wb") as f:
f.write(r.content)
print(f"Saved output.mp4, charged ${Decimal(gen['amount']).scaleb(-6)}")Node.js:見積もり、作成、ポーリング
Node.js 18以降が必要で、パッケージのインストールは不要です。quickstart.mjsとして保存し、MAKERNICE_API_KEYを設定してからnode quickstart.mjsを実行します。
// Node.js 18+ (built-in fetch, no packages). Save as quickstart.mjs and run: node quickstart.mjs
import { randomUUID } from "node:crypto";
import { writeFile } from "node:fs/promises";
const API = "https://api.makernice.com/v1";
const ORIGIN = "https://api.makernice.com";
const HEADERS = { Authorization: `Bearer ${process.env.MAKERNICE_API_KEY}` };
const TIMEOUT_MS = 30_000;
const POLL_INTERVAL_MS = 5_000;
const MAX_WAIT_MS = 30 * 60 * 1000;
const FINAL = new Set(["succeeded", "failed", "canceled"]);
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
// micro-USD integer string -> dollars, no float rounding
const usd = (micro) => `${BigInt(micro) / 1_000_000n}.${(BigInt(micro) % 1_000_000n).toString().padStart(6, "0")}`;
class Stop extends Error {}
async function call(method, path, { body, headers } = {}) {
// Waits out 429 (rate_limited, concurrency_limit_reached) using Retry-After; throws on any other error.
for (let attempt = 0; attempt < 5; attempt++) {
const r = await fetch(API + path, { signal: AbortSignal.timeout(TIMEOUT_MS),
method,
headers: { ...HEADERS, ...(body ? { "Content-Type": "application/json" } : {}), ...headers },
body: body ? JSON.stringify(body) : undefined,
});
if (r.status === 429) {
await sleep(Number(r.headers.get("Retry-After") ?? "5") * 1000);
continue;
}
if (!r.ok) throw new Stop(`${method} ${path} -> ${r.status}: ${await r.text()}`);
return r.json();
}
throw new Stop(`${method} ${path}: still rate limited`);
}
async function main() {
const job = {
"model": "seedance-2.5",
"mode": "t2v",
"prompt": "A slow dolly shot through a greenhouse",
"duration": 5,
"resolution": "720p",
};
// 1. Quote: the exact amount, locked for 12 minutes
const quote = await call("POST", "/generations/quote", { body: job });
console.log(`Quoted $${usd(quote.amount)} (price list v${quote.price_version}), valid until ${quote.expires_at}`);
// 2. Create: same input + quote_id. A retry with the same Idempotency-Key returns the same generation
const idempotencyKey = randomUUID();
let gen = await call("POST", "/generations", { body: { quote_id: quote.id, input: job }, headers: { "Idempotency-Key": idempotencyKey } });
// 3. Poll: queued -> generating -> moderating -> succeeded | failed | canceled (checking: keep polling)
const deadline = Date.now() + MAX_WAIT_MS;
while (!FINAL.has(gen.status)) {
if (Date.now() > deadline) throw new Stop(`${gen.id} is still ${gen.status}; poll it again later`);
await sleep(POLL_INTERVAL_MS);
gen = await call("GET", `/generations/${gen.id}`);
}
if (gen.status !== "succeeded") {
// billing is "released": the hold went back to your balance
throw new Stop(`${gen.status}, billing ${gen.billing}: ${JSON.stringify(gen.error)}`);
}
// 4. Download: outputs[].url is relative to the API origin and answers 302 with a signed file URL.
// Follow it yourself so your API key is never sent to the file host.
const [output] = gen.outputs;
if (output.state === "blocked") throw new Stop("Output blocked by moderation (the run is charged); contact support for a manual review");
const hop = await fetch(ORIGIN + output.url, { headers: HEADERS, redirect: "manual", signal: AbortSignal.timeout(TIMEOUT_MS) });
const location = hop.headers.get("Location");
if (hop.status < 300 || hop.status > 399 || !location) throw new Stop(`GET ${output.url} -> ${hop.status}`);
const file = await fetch(new URL(location, ORIGIN + output.url), { signal: AbortSignal.timeout(120_000) });
if (!file.ok) throw new Stop(`download -> ${file.status}`);
await writeFile("output.mp4", Buffer.from(await file.arrayBuffer()));
console.log(`Saved output.mp4, charged $${usd(gen.amount)}`);
}
main().catch((err) => {
console.error(err instanceof Stop ? err.message : err);
process.exit(1);
});複数の生成を同時に実行するには、並列で開始します(たとえばスレッドプール、非同期HTTPクライアントを使ったasyncio、Promise.allなど)。アカウントごとに同時に進行できる生成は既定で2件で、超えるとAPIはRetry-After付きの429 concurrency_limit_reachedを返し、どちらの例のcall()もその時間だけ待ってから再試行します。上限の引き上げはサポートにお問い合わせください。
認証
GET /v1/modelsとGET /v1/pricingを除き、すべてのリクエストでAuthorizationヘッダーにAPIキーが必要です。APIキーはmn-のあとに英数字48文字が続く形式です。
curl https://api.makernice.com/v1/generations \
-H "Authorization: Bearer $MAKERNICE_API_KEY"- APIキーの全体が表示されるのは作成時の1回だけです。Makerniceはフィンガープリントしか保存しないため、紛失したAPIキーは復元できません。新しいAPIキーを作成し、古いものを削除してください。
- APIキーがない、形式が正しくない、存在しない、無効化・期限切れ・削除済みの場合は、いずれも同じ
401 invalid_api_keyが返ります。無効化や削除は次のリクエストから反映されます。 - APIはサーバーから呼び出してください。ブラウザからのクロスオリジンリクエストは受け付けません(CORS非対応)。APIキーをフロントエンドのコードに含めないでください。
- 同じアカウントのAPIキーはすべて同じ生成を参照します。どのAPIキーでも、コンソールで作成したものを含め、アカウントのすべての生成を取得・一覧・キャンセルできます。
- 同じアカウントのAPIキーはすべて、同じプリペイド残高から課金されます。現在、Makerniceのアカウントはログイン1つにつき1つで、チームは複数のAPIキーを通じて1つの残高を共有します。
- APIキーのページには各キーの累計利用額が表示され、利用状況のページではAPIの利用額をキー別・モデル別に確認できます。
- コンソールでは、APIキーごとに有効期限と利用上限を設定できます。APIキーの利用額は、そのAPIキーで作成した生成の差し引き済み額と現在確保中の額の合計です。上限を超える作成リクエストには
402 key_spend_limit_reachedが返ります。失敗した生成とキャンセルした生成は含まれません。
料金と課金
- 課金は米ドル建てです。金額はすべてmicro-USD(100万分の1ドル)の整数文字列で、
1,000,000= $1.00です。浮動小数点数ではなく、整数または10進数として解析してください。 - 動画は1秒あたり(数量はリクエストした動画の長さ。参照動画クリップ付きのテンプレートではクリップの秒数を含みます)、画像は1リクエストあたり(1回の生成で画像1枚)の料金です。料金 = 単価 × 数量。
- 見積もりは料金を12分間固定します(
expires_at)。その間に料金が変わっても、生成には見積もり額が課金されます。
GET /v1/pricingは現在の料金表の単価を返します。APIキーは不要で、最大60秒キャッシュされることがあります。official_unit_priceがある場合、同じ組み合わせの公式定価を示しますが、比較用です。課金は常にunit_priceで行われます。
{
"price_version": 3,
"data": [
{ "model": "seedance-2.5", "mode": "t2v", "resolution": "720p", "unit": "second", "unit_price": "100000", "unit_price_usd": "0.10", "official_unit_price": "120000" }
]
}失敗した生成は課金されますか?
いいえ。確保額は全額解除され、失敗した生成には課金されません。なお、生成物が出力のモデレーションでポリシー違反としてブロックされた場合は課金されます。サポートに手動での確認を依頼できます。
生成を作成すると、見積もり額が確保されます。確保額は生成が完了した時点で差し引きに切り替わります。失敗やタイムアウトの場合は全額解除され、キュー待ちの間にキャンセルした場合も同様です。解除は返金ではありません。お金は残高から出ていません。
| 何が起きたか | billing | 課金されるか |
|---|---|---|
| 生成が成功した | charged | はい。見積もり額 |
生成が失敗した(error.codeがgeneration_failed、upstream_unavailable、invalid_requestのいずれか) | released | いいえ |
最長実行時間を超えた(error.code: timeout) | released | いいえ |
queuedの間にキャンセルした | released | いいえ |
作成時にプロンプトがブロックされた(422 content_rejected、reason: prompt) | — | いいえ。生成は作成されず、金額も確保されません |
入力画像、またはモデル提供元の安全ポリシーで拒否された(content_rejected、reasonがinput_imageまたはmodel) | released | いいえ |
完成した出力が出力のモデレーションでブロックされた(出力のstate: blocked) | charged | はい。サポートに手動での確認を依頼できます |
結果を確認中(status: checking) | held | 成功が確定した場合のみ |
見積もりを取得
生成の入力を送ると、その生成にかかる正確な金額がレスポンスで分かります。見積もり1件につき、使える生成は1回です。
| フィールド | 型 | 説明 |
|---|---|---|
model | string | 必須。GET /v1/modelsのモデルid。 |
mode | string | 必須。t2v、i2v、flf、r2v、t2i、i2iのいずれか。対応モードはモデルごとに異なります。 |
prompt | string | 必須。標準は1〜4,000文字。これより少ないモデルもあります(GET /v1/modelsのmaxPromptCharsを参照)。 |
duration | integer | 動画のみ。秒数で、モデルの範囲内。省略時はモデルの既定値。 |
resolution | string | モデルのresolutionsのいずれか。省略時はdefaultResolution。モデルに一覧がない場合は指定できません。 |
aspect_ratio | string | モードのaspectRatiosのいずれか。 |
image_url | string | 最初のフレーム。i2vとflfでは必須。 |
last_image_url | string | 最後のフレーム。flfでは必須。 |
reference_images | string[] | r2vとi2iでは必須。最大でモードのmaxReferences枚。 |
seed | integer | 任意。0〜2,147,483,647。同じ設定で同じseedを使うと、より近い結果になります。 |
generate_audio | boolean | 任意。動画に音声トラックを付けるかどうか。 |
prompt_expansion | string | 任意。off、balanced、qualityのいずれか。モデルがプロンプトをどこまで書き換えてよいかを指定します。 |
negative_prompt | string | 任意。避けたい内容を最大2,000文字で記述します。 |
watermark | boolean | 任意。提供元のAI生成ウォーターマークを付けます。 |
最後の5つのフィールドは任意の追加項目です。各モデルが対応するのはその一部だけです。GET /v1/modelsのprices[].optionsに、モードと解像度ごとに使えるものが、指定できる値と既定値とともに載っています。どのモデルルートでも適用できないフィールドには400 invalid_requestが返ります。任意フィールドを変更した場合も、ほかのフィールドと同じく新しい見積もりが必要です。
画像は公開されたhttpsのURL(最大2,048文字)で指定します。モードで使わないフィールドや未知のフィールドは400 invalid_requestで拒否されます。入力フィールドについては、error.detailsに各問題が{ path, message }の形で並びます(ほかのinvalid_requestエラーではdetailsの形が異なるか、含まれないことがあります)。料金のない組み合わせには422 price_unavailableが返ります。
各画像は、静止画のJPEG、PNG、WebPを直接返す、公開アクセス可能なhttpsのURLでなければなりません(アニメーションWebPとGIFは非対応)。サイズは最大10 MBで、そのURLで直接返す必要があります。リダイレクトはたどりません。各画像は1回だけ取得してコピーを保存し、コンテンツのモデレーションとモデルはどちらもそのコピーを使います。画像を使えない場合、生成はinvalid_requestで失敗し、error.inputに該当する画像が示され、確保は解除されます。
curl https://api.makernice.com/v1/generations/quote \
-H "Authorization: Bearer $MAKERNICE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"seedance-2.5","mode":"t2v","prompt":"温室の中をゆっくり進むドリーショット","duration":5,"resolution":"720p"}'{
"id": "qt_6f1c…",
"amount": "500000",
"price_version": 3,
"expires_at": "2026-10-09T09:24:41.000Z",
"facts": { "model": "seedance-2.5", "mode": "t2v", "resolution": "720p", "variant": "default", "unit": "second", "quantity": 5 }
}見積もりは料金を何分間固定しますか?
見積もりは12分間料金を固定し、生成には常に見積もりどおりの金額が課金されます。
| フィールドまたはケース | 意味 |
|---|---|
expires_at | 見積もりの作成から12分後です。それまでに生成を作成してください。 |
amount | 単価 × facts.quantity(動画は秒数、画像は1)で、単位はmicro-USDです。生成を作成すると確保され、成功すると差し引かれます。 |
price_version | 見積もりを取得したときの料金表です。生成を作成する前に新しい料金表が公開されても、見積もり額で課金されます。 |
| 12分を過ぎた場合 | その見積もりで作成すると410 quote_expiredが返ります。金額は確保されません。見積もりを取り直してください。 |
| モデル、モード、解像度、動画の長さを変えた場合 | 422 quote_mismatch。プロンプトだけを変える場合は、見積もりはそのまま有効です。 |
| 使用済みの見積もり | 409 quote_already_used。見積もり1件で作成できる生成は1回です。 |
生成を作成
見積もりと同じ入力で生成を作成します。Idempotency-Keyヘッダーは必須です(べき等性を参照)。
curl https://api.makernice.com/v1/generations \
-H "Authorization: Bearer $MAKERNICE_API_KEY" \
-H "Idempotency-Key: 7c1e9a2b-order-1842" \
-H "Content-Type: application/json" \
-d '{"quote_id":"qt_6f1c…","input":{"model":"seedance-2.5","mode":"t2v","prompt":"温室の中をゆっくり進むドリーショット","duration":5,"resolution":"720p"}}'202で生成が返ります(status: queued、billing: held)。同じIdempotency-Keyで再試行すると、200で元の生成が返ります。- プロンプトは変更できますが、料金に関わる項目(モデル、モード、解像度、動画の長さ)を変えると
422 quote_mismatchが返ります。 - 使用済みの見積もりには
409 quote_already_used、期限切れの見積もりには410 quote_expired、アカウントにない見積もりには404 quote_not_foundが返ります。 - 残高が足りない場合は
402 insufficient_balance、APIキーの利用上限を超える場合は402 key_spend_limit_reachedが返ります。 - トップレベルの
callback_urlは任意です(https、最大2048文字、URLに認証情報を含めない、プライベートアドレスや予約済みアドレスは不可)。生成が終わると、そのURLにPOSTします。アカウントのWebhookアドレスより優先されます。Webhookを参照してください。
ステータスを確認
生成が最終ステータスになるまでポーリングします。数秒おきのポーリングで問題ありません。読み取りの上限は「レート制限」を参照してください。動画の生成には数分かかることがあり、長い動画や高解像度ではさらにかかります。最長実行時間を超えた場合は失敗となり、確保額は全額解除されます。
| ステータス | 意味 |
|---|---|
queued | 開始待ち。まだキャンセルできます。 |
generating | モデルが生成中です。 |
moderating | 生成が終わり、金額は差し引き済みです。出力は安全チェック中で、完了するまでoutputsは空のままです。ポーリングを続けてください。 |
succeeded | 完了。金額は差し引き済みで、outputsが使えます。 |
failed | 完了しませんでした。確保は解除され、errorにコードとメッセージが入ります。 |
canceled | 待機中にキャンセルされました。確保は解除されます。 |
checking | 結果を確認中です。確定するまで金額は確保されたままです。 |
失敗した生成のerror.codeは、invalid_request(モデルがリクエストを拒否しました。プロンプトや設定を調整してください)、content_rejected(コンテンツのモデレーション、またはモデル提供元の安全ポリシーで拒否されました。確保は解除されます)、timeout、upstream_unavailable、generation_failedのいずれかです。content_rejectedの場合、何が拒否されたかをerror.reasonが示します。prompt(プロンプト)、input_image(参照画像)、model(モデル提供元の安全ポリシー)のいずれかです。送信したプロンプトがブロックされたときの422レスポンスにも、同じreasonが入ります。使えない参照画像が原因でinvalid_requestになった場合は、error.inputがその画像を示します。値はimage_url、last_image_url、reference_images[0](0始まり)のいずれかです。その画像を差し替えて、新しい生成を作成してください。
{
"id": "gen_3b9e…",
"type": "video",
"model": "seedance-2.5",
"mode": "t2v",
"status": "succeeded",
"prompt": "温室の中をゆっくり進むドリーショット",
"params": { "duration": 5, "resolution": "720p", "aspectRatio": null },
"amount": "500000",
"billing": "charged",
"price_version": 3,
"outputs": [
{
"index": 0,
"url": "/v1/generations/gen_3b9e…/outputs/0",
"state": "stored",
"contentType": "video/mp4",
"expiresAt": "2026-11-08T09:14:03.000Z"
}
],
"error": null,
"created_at": "2026-10-09T09:12:41.000Z",
"finished_at": "2026-10-09T09:14:03.000Z"
}生成の完了から90日が経つと、データ保持ポリシーに基づきリクエストの内容が消去され、promptは空文字列になります。課金情報(model、params、amount、status、タイムスタンプ)は保持されます。
GET /v1/generations/{id}/eventsは生成のタイムラインを返します。コードはcreated、submitted、generating、delivered、failed_refunded、canceled_refunded、checking、requeuedです。
{
"data": [
{ "code": "created", "at": "2026-10-09T09:12:41.000Z" },
{ "code": "submitted", "at": "2026-10-09T09:12:43.000Z" },
{ "code": "generating", "at": "2026-10-09T09:12:50.000Z" },
{ "code": "delivered", "at": "2026-10-09T09:14:03.000Z" }
]
}生成物をダウンロード
outputsの各項目には、/v1/generations/gen_…/outputs/0のようなurlがあります。これはhttps://api.makernice.comからの相対パスです。APIキーを付けてリクエストすると、ファイルへの有効期間の短いリンクへ302でリダイレクトされます。リダイレクト先を保存せず、ファイルが必要になるたびにこのエンドポイントへリクエストしてください。
curl -L -o video.mp4 https://api.makernice.com/v1/generations/gen_…/outputs/0 \
-H "Authorization: Bearer $MAKERNICE_API_KEY"?download=1を付けると、ファイルが添付ファイルとして返されます。これはファイルがstoredになってから有効です。pendingまたはdegradedの間は元のファイルへリダイレクトされ、このパラメーターは効きません。curl -Lはリダイレクト先のホストにAuthorizationヘッダーを送らないため、APIキーがMakerniceの外に渡ることはありません。- 出力があるのは生成が
succeededの場合だけです。それ以外では404 output_not_foundが返ります。 state:pending(保存中。リンクはすでに使えます)、stored(expiresAtまで保存)、degraded(保存が完了しませんでした。リンクが早めに使えなくなる可能性があるため、早めにダウンロードしてください)、expired(保存したファイルが期限切れ。410 output_expiredが返ります)、blocked(出力がコンテンツのモデレーションを通過しませんでした。urlは空文字列です。サポートに手動での確認を依頼してください)。
キャンセル
キャンセルできるのはqueuedの生成だけです。確保は全額解除され、レスポンスはキャンセル後の生成です。生成がgeneratingになると止められず、キャンセルすると409 not_cancelableが返ります。
curl -X POST https://api.makernice.com/v1/generations/gen_…/cancel \
-H "Authorization: Bearer $MAKERNICE_API_KEY"
# → { "id": "gen_…", "status": "canceled", "billing": "released", … }生成の一覧
アカウントの生成を新しい順に一覧表示します。コンソールで作成したものも含みます。
| クエリ | 説明 |
|---|---|
limit | 1ページの件数。1〜100、既定値20。 |
cursor | 前のページのnext_cursor。 |
status | 1つ以上のステータスをカンマ区切りで指定。例:queued,generating。 |
type | videoまたはimage。 |
model | モデルid。 |
q | 生成idの一部、またはプロンプトに含まれる文字列(どちらも大文字・小文字を区別しません)。 |
curl "https://api.makernice.com/v1/generations?status=queued,generating&limit=50" \
-H "Authorization: Bearer $MAKERNICE_API_KEY"
# → { "data": [ …生成… ], "next_cursor": "gen_…" | null, "total": 12 }totalはフィルターに一致する生成の総数です。next_cursorがnullなら最後のページです。
Webhook
ポーリングの代わりに、生成が最終ステータスになった時点で、Makerniceからサーバーへ生成をPOSTさせることもできます。送信先は生成の作成時に指定したcallback_urlです。指定がなければアカウントのWebhookアドレスに送ります(コンソールの設定で設定します)。どちらもない場合は何も送信しません。
| イベント | 送信されるタイミング |
|---|---|
generation.succeeded | 生成が成功し、差し引きが行われたとき。 |
generation.failed | 生成が失敗し、確保していた金額が解除されたとき。 |
generation.canceled | 生成がキャンセルされ、確保していた金額が解除されたとき。 |
リクエスト
POST /your/webhook/path HTTP/1.1
Content-Type: application/json
Makernice-Signature: t=1791364443,v1=5f0c2d…e81a
Makernice-Event-Id: evt_1b7f…{
"id": "evt_1b7f…",
"type": "generation.succeeded",
"created_at": "2026-10-09T09:14:03.000Z",
"data": {
"id": "gen_3b9e…",
"status": "succeeded",
"billing": "charged",
"outputs": [
{ "index": 0, "url": "https://api.makernice.com/v1/generations/gen_3b9e…/outputs/0", "state": "stored", … }
],
…
}
}dataはGET /v1/generations/{id}と同じ生成オブジェクトです。ただしoutputs[].urlは絶対URLです。Makernice-Event-Idはボディのidと同じ値で、再試行しても変わりません。重複の除外に使ってください。
署名を検証する
Makernice-Signatureはt=<unix seconds>,v1=<hex>の形式です。v1は、t + "." + raw bodyに対して署名シークレットで計算したHMAC-SHA256です。受信した生のバイト列で計算し、定数時間で比較してください。また、tが自分のサーバーの時刻と5分以上ずれているリクエストは拒否してください。署名シークレットはコンソールの設定にあります。callback_urlへの配信も同じシークレットで署名されます。シークレットをローテーションすると、まだ送信されていない配信は新しいシークレットで署名されます。
import { createHmac, timingSafeEqual } from "node:crypto";
// rawBody: the body exactly as received (Buffer). Don't re-serialize parsed JSON.
export function verifyWebhook(rawBody, signatureHeader, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(signatureHeader.split(",").map((p) => [p.slice(0, p.indexOf("=")), p.slice(p.indexOf("=") + 1)]));
const t = Number(parts.t);
if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const expected = createHmac("sha256", secret).update(`${t}.`).update(rawBody).digest();
const given = Buffer.from(parts.v1 ?? "", "hex");
return given.length === expected.length && timingSafeEqual(given, expected);
}import hashlib
import hmac
import time
# raw_body: the body exactly as received (bytes). Don't re-serialize parsed JSON.
def verify_webhook(raw_body: bytes, signature_header: str, secret: str, tolerance_seconds: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in signature_header.split(",") if "=" in p)
try:
t = int(parts["t"])
except (KeyError, ValueError):
return False
if abs(time.time() - t) > tolerance_seconds:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))配信と再試行
- HTTPSのみ。リダイレクトはたどりません。各試行は10秒でタイムアウトします。
- 2xxのレスポンスであれば配信完了とみなします。すぐに応答し、重い処理はそのあとで行ってください。
- それ以外の場合は1 min、5 min、30 min、2 h、6 h、12 h後に再試行し(合計7回)、それでも届かなければ配信失敗とします。最近の配信と最後のレスポンスはコンソールの設定で確認できます。
- 通常、1つの生成につきイベントは1回送信されますが、同じ配信が複数回届くことがあります(たとえば受信側でタイムアウトした場合)。コンテンツの確認で保留された出力があとから公開された場合は、新しいイベントidで生成を再送するため、1つの生成についてイベントが2回届くことがあります。最新のものが優先です。
モデル
GET /v1/modelsは、モデルと、使えるモード・解像度・動画の長さを一覧表示します。料金が設定されているモードだけが載り、その料金はmodes[].pricesにあります。APIキーは不要で、最大60秒キャッシュされることがあります。
resolutions/defaultResolution:resolutionに指定できる値。一覧が空の場合、モデルの解像度は固定で、resolutionは省略します(固定の値と同じでも、送ると400 invalid_requestが返ります)。固定の解像度はmodes[].prices[].resolutionに示されます。たとえばseedance-2.0-fastは常に720pで出力されます。一覧にある解像度でも、モードによっては料金がない場合があります。modes[].pricesを確認してください。その解像度で見積もると422 price_unavailableが返ります。durations:動画では秒単位の{ min, max, default }、画像ではnull。modes[].needs:モードに必要な画像(image→image_url、lastImage→last_image_url、references→reference_images、最大maxReferences枚)。modes[].aspectRatios:指定できるaspect_ratioの値。modes[].prices:解像度ごとの単価。
{
"data": [
{
"id": "seedance-2.5",
"type": "video",
"resolutions": ["480p", "720p", "1080p", "4k"],
"defaultResolution": "720p",
"durations": { "min": 4, "max": 30, "default": 5 },
"modes": [
{
"mode": "t2v",
"needs": { "image": false, "lastImage": false, "references": false },
"maxReferences": null,
"aspectRatios": ["adaptive", "16:9", "9:16", "1:1", "4:3", "3:4", "21:9", "9:21"],
"prices": [{ "resolution": "720p", "unit": "second", "unitPrice": "100000" }]
}
]
}
]
}どのモデルを呼び出せますか?
ひとつのAPIキーで、ByteDance、Kuaishou、MiniMax、Alibabaの動画モデル13件と画像モデル4件、合わせて17のモデルをすべて呼び出せます。Seedance、Kling、Wan、MiniMax H3などを含みます。どのモデルも同じリクエスト形式で、modelに下表のAPIモデルidを指定します。
| モデル | 提供元 | APIモデルid | 種類 |
|---|---|---|---|
| Seedance 2.5 | ByteDance | seedance-2.5 | 動画 |
| Seedance 2.0 | ByteDance | seedance-2.0 | 動画 |
| Seedance 2.0 Fast | ByteDance | seedance-2.0-fast | 動画 |
| MiniMax H3 | MiniMax | minimax-h3 | 動画 |
| MiniMax H3 Max | MiniMax | minimax-h3-max | 動画 |
| MiniMax H3 Max Turbo | MiniMax | minimax-h3-max-turbo | 動画 |
| Wan 3.0 | Alibaba | wan-3.0 | 動画 |
| Wan 3.0 Prime | Alibaba | wan-3.0-prime | 動画 |
| Kling 3.0 Pro | Kuaishou | kling-3.0-pro | 動画 |
| Kling 3.0 Standard | Kuaishou | kling-3.0-standard | 動画 |
| Kling 3.0 4K | Kuaishou | kling-3.0-4k | 動画 |
| Kling 4.0 | Kuaishou | kling-4.0 | 動画 |
| Happy Horse 1.1 | Alibaba | happy-horse-1.1 | 動画 |
| Seedream 5.0 Pro | ByteDance | seedream-5.0-pro | 画像 |
| Seedream 5.0 | ByteDance | seedream-5.0 | 画像 |
| Qwen3 Image | Alibaba | qwen3-image | 画像 |
| Qwen3 Pro Image | Alibaba | qwen3-pro-image | 画像 |
用途別の対応モードと料金の例は、次のページ(いずれも英語)で確認できます:画像から動画(英語)、AI動画ジェネレーター(英語)、AI画像ジェネレーター(英語)。
エラー
エラーはJSONで返ります:{ "error": { "code", "message", "reason"?, "details"?, "request_id" } }。処理の分岐にはcodeを使ってください。messageは変わることがあります。すべてのレスポンスにx-request-idヘッダー(request_idと同じ値)が付きます。サポートに問い合わせる際に添えていただければ、該当のリクエストを特定できます。
| ステータス | コード | 対処 |
|---|---|---|
| 400 | invalid_request | リクエストボディ、クエリ、またはヘッダーが不正です。detailsに各フィールドと問題点が示されるので、リクエストを修正してください。 |
| 400 | template_conflict | テンプレートでのリクエストに指定できるのはtemplate、imagesと、任意のprompt、resolution、aspect_ratioだけです。モデル、モード、動画の長さなどの設定はテンプレートで固定されます。それ以外のフィールドを削除してください。 |
| 401 | invalid_api_key | APIキーがない、形式が正しくない、無効化・期限切れ・削除済みのいずれかです。 |
| 402 | insufficient_balance | ウォレットにチャージしてください。 |
| 402 | key_spend_limit_reached | このAPIキーの利用上限を引き上げるか、別のAPIキーを使ってください。 |
| 402 | daily_spend_limit_reached | アカウントが1日の利用上限に達しました。毎日00:00 UTCにリセットされます。引き上げが必要な場合はサポートにお問い合わせください。 |
| 403 | org_frozen | アカウントが凍結されています。サポートにお問い合わせください。 |
| 404 | quote_not_found | この見積もりはアカウント内に存在しません。新しい見積もりを取得してください。 |
| 404 | task_not_found | この生成はアカウント内に存在しません。idを確認してください。 |
| 404 | output_not_found | 生成がまだ成功していないか、そのインデックスの出力がありません。 |
| 404 | template_not_found | このidのテンプレートはありません。利用できるテンプレートはGET /v1/templatesで確認できます。 |
| 404 | not_found | 存在しないエンドポイントです。メソッドとパスを確認してください。 |
| 409 | quote_already_used | 新しい見積もりを取得してください。 |
| 409 | not_cancelable | キャンセルできるのは待機中の生成だけです。 |
| 410 | quote_expired | 新しい見積もりを取得してください。 |
| 410 | output_expired | 保存されたファイルは期限切れのため、ダウンロードできません。 |
| 410 | template_retired | このテンプレートは提供を終了しました。GET /v1/templatesから別のテンプレートを選んでください。作成済みの生成には影響しません。 |
| 413 | payload_too_large | リクエストボディが64 KBを超えています。プロンプトを短くするか、送る画像URLを減らしてください。 |
| 422 | idempotency_conflict | このIdempotency-Keyは別のリクエストで使用済みです。新しい値を使ってください。 |
| 422 | quote_mismatch | 入力が料金に関わる項目(モデル、モード、解像度、動画の長さ)を変えています。この入力で見積もりを取り直してください。 |
| 422 | price_unavailable | この組み合わせには料金が設定されていません。GET /v1/modelsに載っている組み合わせを選んでください。 |
| 422 | content_rejected | コンテンツのモデレーション、またはモデル提供元の安全ポリシーで拒否されました。何が拒否されたかはerror.reason(prompt、input_image、model)で分かります。プロンプトや画像を変更してください。今回は課金されていません。 |
| 429 | rate_limited | Retry-Afterの秒数だけ待ってから再試行してください。 |
| 429 | concurrency_limit_reached | アカウントで進行中の生成が多すぎます。いずれかが終わるまで待つか(Retry-Afterを参照)、上限の引き上げをサポートに依頼してください。 |
| 500 | internal_error | 予期しないサーバーエラーです。バックオフを入れて再試行してください。サポートに問い合わせる際はrequest_idを添えてください。 |
予期しない5xxレスポンスやネットワークエラーの場合は、バックオフを入れて再試行してください。作成リクエストを再試行するときは同じIdempotency-Keyを使えば、二重に課金されることはありません。
レート制限
上限は1分あたりです。超えると429 rate_limitedがRetry-Afterヘッダー(秒)付きで返ります。
| 対象 | 上限 |
|---|---|
| APIキーごと:書き込み(見積もり、作成、キャンセル) | 60回/分 |
| APIキーごと:読み取り(取得、一覧、イベント、出力) | 600回/分 |
| アカウントごと:全APIキーの書き込み合計 | 120回/分 |
| IPごと:モデル、料金、拒否されたAPIキーでのリクエスト | 120回/分 |
また、1つのAPIキーで同時に進行できる作成リクエストは最大4件です。超えた場合も429が返ります。上限は調整されることがあります。
各アカウントには利用上限が2つあり、既定では同時に進行できる生成が2件、UTCの1日あたりの利用額がUS$100です。超えると429 concurrency_limit_reached(Retry-After付き)または402 daily_spend_limit_reachedが返ります。引き上げが必要な場合はサポートにお問い合わせください。生成は、queued、generating、checkingの間、どのモデル・どのAPIキーで作成されたかにかかわらず、同時進行の枠に数えられます。moderating、succeeded、failed、canceledになると数えられなくなります。ウェブアプリで開始した生成も同じ枠に数えられます。
冪等性
POST /v1/generationsにはIdempotency-Keyヘッダーが必須です。1〜200文字の印字可能なASCII文字で、スペースは使えません。新しい生成ごとに、UUIDなどの新しい値を使ってください。- この値はアカウント内で一意で、アカウントのすべてのAPIキーで共有されます。
- 同じ値・同じ見積もり・同じ入力でリクエストを繰り返すと、
200で元の生成が返り、再度課金されることはありません。 - 同じ値で、異なる
quote_id、異なるinput(プロンプトや画像URLを含む)、または異なるcallback_urlを送ると、422 idempotency_conflictが返ります。 - 見積もりとキャンセルにはIdempotency-Keyを使いません。すでに成功したキャンセルを繰り返すと
409 not_cancelableが返ります。GETでステータスを確認してください。
よくある質問
見積もりに費用はかかりますか?
POST /v1/generations/quoteは入力の料金を算出するだけです。その見積もりで生成を作成するまで、金額の確保や課金は行われません。モデルごとに別のAPIキーが必要ですか?
modelフィールドを変更します。