コンテンツにスキップ
無料で登録
API リファレンス

動画 API:見積もり、生成、状態確認、ダウンロード

BetterToken API Key と再開可能な Python コードで、十種類のモデルの見積もり、非同期生成、状態確認、MP4 ダウンロードを行います。

動画生成は非同期です。モデルの能力を確認して見積もりを取得し、タスクを作成します。ID を保存し、状態を確認して、動画の準備ができたらダウンロードします。

接続情報

設定値
Base URLhttps://bettertoken.ai/v1
認証Authorization: Bearer YOUR_API_KEY
利用できるモデルを取得GET /v1/video-models
生成前の見積もりPOST /v1/videos/quote
タスクを作成POST /v1/videos
タスクを確認GET /v1/videos/{id}
動画をダウンロードGET /v1/videos/{id}/content
互換の作成先POST /v1/video/generations

利用できるモデルを取得

ターミナルウィンドウ
export BETTERTOKEN_API_KEY="YOUR_API_KEY"
curl --fail-with-body "https://bettertoken.ai/v1/video-models" \
-H "Authorization: Bearer $BETTERTOKEN_API_KEY"

カタログの各項目は id と operation の組み合わせで識別します。各項目の fields、media_limits、constraints に従ってください。すべての操作に同じ設定は使えません。

シリーズ別の開始設定

シリーズサンプルの設定
Seedance CN5 秒、720p、16:9、音声オフ
Kling 3.05 秒、16:9、標準モード。共通の解像度フィールドは送信しない
MiniMax H35 秒、768p、16:9。音声はモデルの既定値
Wan 3.0 / Prime5 秒、720p、16:9、音声オフ
Wan 2.7 T2V / I2Vテキスト:5 秒、720p、16:9。画像:5 秒、720p、比率は入力に従う

これはコードの開始設定であり、各モデルの全上限ではありません。シリーズを変えるときはモデル名以外の変更も必要です。十種類の正確な ID と操作は モデル一覧.

Python の完全な処理例

Python 3 を使い、追加の依存パッケージは不要です。環境変数を設定し、コードを video_example.py として保存して実行します。見積もりでは生成を開始しません。GENERATE と入力すると実際のタスクが作成され、料金が発生する場合があります。

ターミナルウィンドウ
export BETTERTOKEN_API_KEY="YOUR_API_KEY"
export VIDEO_MODEL="wan-2.7-t2v"
export VIDEO_STATE_FILE="video-state.json"
python3 video_example.py

新しいタスクには新しい VIDEO_STATE_FILE を使います。同じタスクを再試行するときは、リクエスト、BetterToken API Key、状態ファイルを維持してください。コードは Idempotency-Key または保存済みタスク ID を再利用します。待機上限はクライアントの設定であり、生成時間の保証ではありません。

import hashlib
import json
import os
import shutil
import time
import uuid
from pathlib import Path
from urllib.error import HTTPError
from urllib.request import Request, urlopen
base_url = "https://bettertoken.ai/v1"
api_key = os.environ["BETTERTOKEN_API_KEY"]
model = os.getenv("VIDEO_MODEL", "wan-2.7-t2v")
state_path = Path(os.getenv("VIDEO_STATE_FILE", "video-state.json"))
sample_image = "https://docs.bettertoken.ai/images/nano-banana-guide/sample-07.png"
body = {
"model": "wan-2.7-t2v",
"operation": "text_to_video",
"prompt": "A blue ceramic mug on a white studio table. The camera slowly moves closer. Soft daylight.",
"duration": {
"mode": "fixed",
"seconds": 5
},
"output": {
"resolution": "720p",
"aspect_ratio": "16:9",
"format": "mp4"
}
}
seedance_models = {
"seedance-2.5-cn", "seedance-2.0-cn",
"seedance-2.0-fast-cn", "seedance-2.0-mini-cn",
}
allowed_models = seedance_models | {
"kling-3.0", "minimax-h3", "wan-3.0", "wan-3.0-prime",
"wan-2.7-t2v", "wan-2.7-i2v",
}
if model not in allowed_models:
raise ValueError("Choose a Model ID from the documented batch")
body["model"] = model
if model in seedance_models or model in {"wan-3.0", "wan-3.0-prime"}:
body["audio"] = {"mode": "off"}
elif model == "kling-3.0":
body["output"].pop("resolution")
body["audio"] = {"mode": "off"}
body["provider_options"] = {"kling": {"mode": "std"}}
elif model == "minimax-h3":
body["output"]["resolution"] = "768p"
elif model == "wan-2.7-i2v":
body["operation"] = "image_to_video"
body["prompt"] = "The camera slowly moves around the blue mug. Preserve its shape and the background."
body["output"].pop("aspect_ratio")
body["media"] = [{
"id": "first-frame", "type": "image", "role": "first_frame",
"source": os.getenv("VIDEO_IMAGE_URL", sample_image),
}]
request_file = os.getenv("VIDEO_REQUEST_FILE")
if request_file:
body = json.loads(Path(request_file).read_text(encoding="utf-8"))
def api(method, path, payload=None, extra_headers=None):
headers = {"Authorization": f"Bearer {api_key}"}
headers.update(extra_headers or {})
data = None
if payload is not None:
data = json.dumps(payload).encode("utf-8")
headers["Content-Type"] = "application/json"
request = Request(base_url + path, data=data, headers=headers, method=method)
try:
with urlopen(request, timeout=60) as response:
return json.load(response)
except HTTPError as error:
message = error.read().decode("utf-8", errors="replace")
raise RuntimeError(f"HTTP {error.code}: {message}") from error
digest = hashlib.sha256(json.dumps(body, sort_keys=True).encode("utf-8")).hexdigest()
if state_path.exists():
state = json.loads(state_path.read_text(encoding="utf-8"))
if state["request_sha256"] != digest:
raise ValueError("Request changed. Use a new VIDEO_STATE_FILE for a new task")
else:
state = {"request_sha256": digest, "idempotency_key": str(uuid.uuid4())}
state_path.write_text(json.dumps(state), encoding="utf-8")
task_id = state.get("task_id")
if not task_id:
if not state.get("create_attempted"):
catalogue = api("GET", "/video-models")
if not any(row["id"] == body["model"] and row["operation"] == body["operation"]
for row in catalogue["data"]):
raise RuntimeError("This model and operation are not in the current catalogue")
quote = api("POST", "/videos/quote", body)
print(json.dumps(quote, ensure_ascii=False, indent=2))
if input("Review the quote. Type GENERATE to create a paid task: ") != "GENERATE":
raise SystemExit("No generation request sent")
if time.time() >= quote["expires_at"]:
raise RuntimeError("Quote expired. Re-run to request a new quote")
state["create_attempted"] = True
state_path.write_text(json.dumps(state), encoding="utf-8")
task = api("POST", "/videos", body, {"Idempotency-Key": state["idempotency_key"]})
task_id = task["id"]
state["task_id"] = task_id
state_path.write_text(json.dumps(state), encoding="utf-8")
print("Task ID:", task_id)
deadline = time.monotonic() + 1800
while time.monotonic() < deadline:
task = api("GET", f"/videos/{task_id}")
print(task["status"], task["stage"], task["delivery_status"], task["financial_status"])
if task["status"] in {"failed", "canceled"}:
raise RuntimeError(json.dumps(task.get("error"), ensure_ascii=False))
if task["delivery_status"] in {"expired", "unavailable"}:
raise RuntimeError("Video download is no longer available")
if task["status"] == "completed" and task["delivery_status"] == "ready":
print(json.dumps(task["billing"], ensure_ascii=False, indent=2))
break
time.sleep(5)
else:
raise TimeoutError("Task still pending. Re-run with the same state file to resume")
request = Request(base_url + f"/videos/{task_id}/content",
headers={"Authorization": f"Bearer {api_key}"})
with urlopen(request, timeout=120) as response:
if response.headers.get_content_type() != "video/mp4":
raise RuntimeError("Expected an MP4 response; inspect the task before retrying")
with Path("video.mp4").open("wb") as output:
shutil.copyfileobj(response, output)
print("Saved video.mp4")

画像から動画を生成

VIDEO_MODEL を wan-2.7-i2v にすると、次の公開サンプル画像を使います。VIDEO_IMAGE_URL で、利用権がありサーバーが取得できる HTTPS 画像に変更できます。この入口では output.aspect_ratio を送信しません。

動画生成の入力画像:白い背景に置かれた青い陶器のマグカップ
{
"model": "wan-2.7-i2v",
"operation": "image_to_video",
"prompt": "The camera slowly moves around the blue mug. Preserve the mug shape and the white background.",
"duration": {
"mode": "fixed",
"seconds": 5
},
"output": {
"resolution": "720p",
"format": "mp4"
},
"media": [
{
"id": "first-frame",
"type": "image",
"role": "first_frame",
"source": "https://docs.bettertoken.ai/images/nano-banana-guide/sample-07.png"
}
]
}

参照生成と独自リクエスト

次の JSON を request.json に保存し、VIDEO_REQUEST_FILE=request.json を設定します。新しいタスクには新しい状態ファイルを使います。コードは同じリクエストを見積もり、確認、作成し、状態確認とダウンロードを行います。素材はモデルの種類とサイズの条件を満たす必要があります。

{
"model": "seedance-2.5-cn",
"operation": "reference_to_video",
"prompt": "Create a studio product video of the blue mug in the reference image. Keep the mug design consistent.",
"duration": {
"mode": "fixed",
"seconds": 5
},
"output": {
"resolution": "720p",
"aspect_ratio": "16:9",
"format": "mp4"
},
"audio": {
"mode": "off"
},
"media": [
{
"id": "reference-1",
"type": "image",
"role": "reference_image",
"source": "https://docs.bettertoken.ai/images/nano-banana-guide/sample-07.png"
}
]
}

H3 の結果を再生成

プレースホルダーを、自分の完了済みでダウンロード可能な 768p H3 タスク ID に置き換えてください。元タスクは再生成条件を満たす必要があります。これは上の独自リクエストを使う新しい有料タスクです。新しいプロンプト、長さ、出力設定、素材は追加しません。サーバーが元タスクを再利用し、条件を確認します。

{
"model": "minimax-h3",
"operation": "video_edit",
"provider_options": {
"hailuo": {
"source_task_id": "YOUR_COMPLETED_H3_TASK_ID"
}
}
}

完了の確認

フィールド・状態操作
202受理済み。動画の完成ではない
queued / in_progress待機中または実行中。確認を続ける
completed + delivery_status: readyダウンロード可能
failed / canceled確認を止め、エラーを読む。自動で新規作成しない
delivery_status: expired / unavailableこのタスクの動画はダウンロードできない
financial_status精算は生成やダウンロードの状態と別。確認を待つ

見積もりと精算を読む

currency は見積もり通貨です。生成 JSON に通貨を入れないでください。reserved_amount は予約額、max_charge_amount は見積もり上限です。タスク作成時に再計算し、条件を固定します。精算は billing.currency、billing.net_amount、financial_status で確認します。金額は十進文字列です。欠落や null は無料を意味しません。現在の価格は 現在のモデルと料金.

よくあるエラー

状態次の操作
401API Key を確認。JSON や公開ログに含めない
409元の冪等キーとリクエストを保持。既知のタスク ID を先に確認
422エラーのフィールド位置と許可値に従って修正
410動画の期限切れ。次回は期限内にダウンロード
429 / 503 / timeout既存タスクを先に確認。作成結果が不明なら元の冪等キーを使い、新しいキーで自動再送しない