AI動画APIのエラーコード:意味と再試行すべきタイミング
動画生成ジョブは通常のAPI呼び出しより失敗しやすく、モデルの混雑、プロンプトの拒否、タイムアウトなどが起こります。このガイドでは、返ってくるすべてのエラー、再試行する価値のあるもの、安全に再試行する方法を紹介します。
エラーが現れる2つの場面
動画・画像のジョブは2つのタイミングで失敗する可能性があり、コードは両方に対応する必要があります。
- 送信時:
POST /v1/generationsがHTTPエラーステータスと{"error": {"code": "...", "message": "..."}}のようなボディを返します。ジョブは開始されず、課金もされません。 - 実行中:送信は
202で成功しても、ジョブが後からstatus: "failed"で終了します。そのerrorにはcode、依頼者に表示できるmessage、そしてretryableが含まれます。
失敗したジョブは、どのモデルでも課金されません。料金は送信時に残高から確保され、ジョブが失敗すると解放されます。
送信時のHTTPエラー
| ステータス | コード | 意味 | 再試行 |
|---|---|---|---|
| 400 | invalid_request | ボディがJSONでない、model または input がない、またはAPIが認識しないフィールドがあります。 | 不可。ボディを修正してください。 |
| 401 | unauthorized | APIキーがないか、誤っています。 | 不可。キーを確認してください。 |
| 402 | insufficient_credits | ジョブのために確保する料金を残高でまかなえません。 | チャージ後に可。 |
| 403 | account_disabled, generation_not_enabled | アカウントが一時停止中か、キーが料金の参照のみ可能で生成はできません。 | 不可。サポートに連絡してください。 |
| 404 | not_found | お使いのアカウントに、そのIDの生成はありません。 | 不可。 |
| 409 | idempotency_conflict | Idempotency-Key がすでに別のボディで使用されています。 | 新しいキーで可。 |
| 409 | not_ready | ジョブが成功する前にファイルを要求しました。 | 可。成功した後であれば。 |
| 413 | body_too_large | JSONボディが1 MBを超えています。ファイルはアップロードして、リンクを送ってください。 | 不可。ボディを小さくしてください。 |
| 422 | invalid_input | モデルが受け付けない値です。たとえば、一覧にない長さやサイズです。どの値かはメッセージに示されます。 | 不可。値を変更してください。 |
| 422 | max_cost_exceeded | すべてのルートが max_cost_usd を上回る料金です。 | 不可。上限を引き上げるか、設定を下げてください。 |
| 422 | model_not_available | そのIDのモデルはありません。 | 不可。IDを確認してください。 |
| 429 | rate_limited | 1つのアカウントから同じ1秒間に送られたリクエストが多すぎます。 | 可。Retry-After の後に。 |
| 429 | concurrency_limit | アカウントで実行中のジョブがすでに上限数に達しています。 | 可。1つ完了したら。 |
| 500 | server_error | こちら側で問題が発生しました。 | 可。同じ Idempotency-Key で。 |
| 503 | busy | 一時的な負荷です。 | 可。Retry-After の後に、同じキーで。 |
| 503 | model_unavailable | このモデルで現在利用できるルートがありません。 | 可。しばらくしてから。 |
目安:429、500、503 はバックオフを入れて再試行する価値があります。4xx の範囲は、429を除き、リクエスト自体を変更する必要があります。
ジョブが開始後に失敗する理由
| error.code | retryable | 発生した内容 |
|---|---|---|
content_policy | no | モデルがプロンプトまたはメディアを拒否したか、結果を差し止めました。reason に理由が入ります:sexual、minors、copyright、real_person、other。 |
invalid_input | no | 参照ファイルを読み取れなかったか、モデルが値を拒否しました。どちらかはメッセージに示されます。 |
model_declined | no | モデルが画像や動画を作らず、文章で応答しました。プロンプトには、結果そのものを描写してください。 |
reference_changed | no | 参照ファイルが、チェック後に変更されました。もう一度送信してください。 |
max_cost_exceeded | no | 実行できる唯一のルートが、上限を超える料金です。 |
model_busy | yes | すべてのルートが満杯でした。1分ほど待って再試行してください。 |
model_unavailable | yes | 現在、このジョブを受け付けられるルートがありません。 |
timeout | yes | 制限時間内に結果を返したルートがないか、max_wait_s を過ぎました。 |
all_routes_failed | yes | すべてのルートが、各ルート側の理由で失敗しました。 |
内部では、混雑または障害のあるルートに当たったジョブは自動的に次のルートへ移ります。そのため model_busy や all_routes_failed が表示された時点で、すべてのルートはすでに試行済みです。attempts フィールドに、その数が入ります。通常は1〜2分待ってから再試行すれば十分です。
コンテンツの拒否
content_policy は、最もよく遭遇する失敗です。判断するのはAPIではなくモデルで、reason にどのルールが適用されたかが示されます。よくある原因は、プロンプトや参照画像に含まれる有名人の名前や顔(real_person)、ブランドや有名キャラクター(copyright)、そして未成年者が関わると読める内容です。同じリクエストを再試行しても解決しません。プロンプトの表現を変えるか、画像を変更してください。厳しさはモデルごとに異なるため、同じプロンプトでも、あるモデルでは通り、別のモデルでは拒否されることがあります。
適切に動作する再試行ループ
このPythonヘルパーは、再試行する価値のあるものだけを再試行し、そうでないものでは停止します。重要な点は2つです。
503や接続切断の後に送信し直すときは、同じIdempotency-Keyを使うため、課金される2つ目のジョブが開始されることはありません。- ジョブが失敗した後にやり直すときは、新しいキーを使います。同じキーでは、失敗したジョブがそのまま返されるだけです。
import os
import time
import uuid
import requests
API = "https://api.revana.si/v1"
AUTH = {"Authorization": f"Bearer {os.environ['REVANA_API_KEY']}"}
RETRY_HTTP = {429, 500, 503}
class GenerationError(Exception):
pass
def submit(body):
"""POST a job. Retries 429, 500 and 503 with the same Idempotency-Key, so a retry never starts a second job."""
headers = {**AUTH, "Idempotency-Key": str(uuid.uuid4())}
for attempt in range(6):
r = requests.post(f"{API}/generations", headers=headers, json=body, timeout=60)
if r.ok:
return r.json()
if r.status_code in RETRY_HTTP:
time.sleep(float(r.headers.get("Retry-After", 2 ** attempt)))
continue
err = r.json()["error"] # 400, 401, 402, 403, 409, 413, 422: fix the request, do not retry
raise GenerationError(f"{r.status_code} {err['code']}: {err['message']}")
raise GenerationError("still busy after 6 tries")
def wait(job):
while job["status"] in ("queued", "running"):
time.sleep(5)
job = requests.get(f"{API}/generations/{job['id']}", headers=AUTH).json()
return job
def generate(body, tries=3):
for n in range(tries):
job = wait(submit(body)) # each call uses a new key: a failed job is final
if job["status"] == "succeeded":
return job
err = job["error"]
if not err["retryable"]:
raise GenerationError(f"{err['code']}: {err['message']}")
time.sleep(60 * (n + 1)) # model_busy, timeout and the like: wait, then try again
raise GenerationError(f"gave up after {tries} tries: {err['code']}")
job = generate({
"model": "seedance-2.0-fast",
"input": {"prompt": "Waves roll onto black sand at sunset.", "resolution": "480p", "duration": 5},
"max_cost_usd": 0.3,
})
print(job["output"]["urls"][0], job["cost_usd"])サンプルのジョブは、5秒・480pのSeedance 2.0 Fastクリップで、成功した場合は $0.25、失敗した場合は無料です。max_cost_usd の上限により、設定ミスで想定以上の料金がかかることを防げます。
そもそもエラーを避けるには
- 送信前にオプションを確認する。
GET /v1/modelsとモデルページに、各モデルのサイズ、長さ、参照の上限が掲載されています。範囲外の値は、422 invalid_inputですぐに拒否されます。 - ファイルは埋め込まずにアップロードする。
POST /v1/uploadsは、画像は30 MB、動画は50 MB、音声は15 MBまで受け付け、14日間有効なリンクを返すため、リクエストボディを小さく保てます。 - コードで残高を確認する。大量のバッチの前に
GET /v1/balanceを呼び出せば、402エラーの連発を防げます。クレジットパックは料金ページにあります。
成功したジョブのコストを下げる方法は、AI動画APIのコストを下げる方法をご覧ください。エラーの詳細はドキュメントにあります。試すにはAPIキーを取得してください。
よくある質問
AI動画の生成が失敗した場合、課金されますか?
いいえ。料金は送信時に確保され、理由を問わずジョブが失敗すると解放されます。
どのエラーを再試行すべきですか?
送信時は、429、500、503を Retry-After の待機後に再試行します。ジョブが失敗した場合は、error.retryable が true のとき(model_busy、model_unavailable、timeout、all_routes_failed)に再試行します。
content_policy とは何を意味しますか?
モデルがプロンプトまたはメディアを拒否したか、生成物を差し止めたことを示します。reason フィールドに該当するルール(sexual、minors、copyright、real_person、other)が入ります。再試行せず、リクエストの内容を変更してください。
二重課金されずに再試行するには?
Idempotency-Key ヘッダーを送り、タイムアウトや503の後に再送するときも同じキーを使います。2つ目ではなく、同じ生成結果が返されます。
422 invalid_input とは何を意味しますか?
送信した値(長さ、サイズ、参照画像の枚数など)をモデルが受け付けません。メッセージにフィールド名と受け付ける値が示されます。