仕組み

AI動画APIのエラーコード:意味と再試行すべきタイミング

動画生成ジョブは通常のAPI呼び出しより失敗しやすく、モデルの混雑、プロンプトの拒否、タイムアウトなどが起こります。このガイドでは、返ってくるすべてのエラー、再試行する価値のあるもの、安全に再試行する方法を紹介します。

エラーが現れる2つの場面

動画・画像のジョブは2つのタイミングで失敗する可能性があり、コードは両方に対応する必要があります。

  1. 送信時:POST /v1/generations がHTTPエラーステータスと {"error": {"code": "...", "message": "..."}} のようなボディを返します。ジョブは開始されず、課金もされません。
  2. 実行中:送信は 202 で成功しても、ジョブが後から status: "failed" で終了します。その error には code、依頼者に表示できる message、そして retryable が含まれます。

失敗したジョブは、どのモデルでも課金されません。料金は送信時に残高から確保され、ジョブが失敗すると解放されます。

送信時のHTTPエラー

generationsエンドポイントのHTTPエラーコード
ステータスコード意味再試行
400invalid_requestボディがJSONでない、model または input がない、またはAPIが認識しないフィールドがあります。不可。ボディを修正してください。
401unauthorizedAPIキーがないか、誤っています。不可。キーを確認してください。
402insufficient_creditsジョブのために確保する料金を残高でまかなえません。チャージ後に可。
403account_disabled, generation_not_enabledアカウントが一時停止中か、キーが料金の参照のみ可能で生成はできません。不可。サポートに連絡してください。
404not_foundお使いのアカウントに、そのIDの生成はありません。不可。
409idempotency_conflictIdempotency-Key がすでに別のボディで使用されています。新しいキーで可。
409not_readyジョブが成功する前にファイルを要求しました。可。成功した後であれば。
413body_too_largeJSONボディが1 MBを超えています。ファイルはアップロードして、リンクを送ってください。不可。ボディを小さくしてください。
422invalid_inputモデルが受け付けない値です。たとえば、一覧にない長さやサイズです。どの値かはメッセージに示されます。不可。値を変更してください。
422max_cost_exceededすべてのルートが max_cost_usd を上回る料金です。不可。上限を引き上げるか、設定を下げてください。
422model_not_availableそのIDのモデルはありません。不可。IDを確認してください。
429rate_limited1つのアカウントから同じ1秒間に送られたリクエストが多すぎます。可。Retry-After の後に。
429concurrency_limitアカウントで実行中のジョブがすでに上限数に達しています。可。1つ完了したら。
500server_errorこちら側で問題が発生しました。可。同じ Idempotency-Key で。
503busy一時的な負荷です。可。Retry-After の後に、同じキーで。
503model_unavailableこのモデルで現在利用できるルートがありません。可。しばらくしてから。

目安:429、500、503 はバックオフを入れて再試行する価値があります。4xx の範囲は、429を除き、リクエスト自体を変更する必要があります。

ジョブが開始後に失敗する理由

失敗した生成のエラーコード
error.coderetryable発生した内容
content_policynoモデルがプロンプトまたはメディアを拒否したか、結果を差し止めました。reason に理由が入ります:sexual、minors、copyright、real_person、other。
invalid_inputno参照ファイルを読み取れなかったか、モデルが値を拒否しました。どちらかはメッセージに示されます。
model_declinednoモデルが画像や動画を作らず、文章で応答しました。プロンプトには、結果そのものを描写してください。
reference_changedno参照ファイルが、チェック後に変更されました。もう一度送信してください。
max_cost_exceededno実行できる唯一のルートが、上限を超える料金です。
model_busyyesすべてのルートが満杯でした。1分ほど待って再試行してください。
model_unavailableyes現在、このジョブを受け付けられるルートがありません。
timeoutyes制限時間内に結果を返したルートがないか、max_wait_s を過ぎました。
all_routes_failedyesすべてのルートが、各ルート側の理由で失敗しました。

内部では、混雑または障害のあるルートに当たったジョブは自動的に次のルートへ移ります。そのため model_busy や all_routes_failed が表示された時点で、すべてのルートはすでに試行済みです。attempts フィールドに、その数が入ります。通常は1〜2分待ってから再試行すれば十分です。

コンテンツの拒否

content_policy は、最もよく遭遇する失敗です。判断するのはAPIではなくモデルで、reason にどのルールが適用されたかが示されます。よくある原因は、プロンプトや参照画像に含まれる有名人の名前や顔(real_person)、ブランドや有名キャラクター(copyright)、そして未成年者が関わると読める内容です。同じリクエストを再試行しても解決しません。プロンプトの表現を変えるか、画像を変更してください。厳しさはモデルごとに異なるため、同じプロンプトでも、あるモデルでは通り、別のモデルでは拒否されることがあります。

適切に動作する再試行ループ

このPythonヘルパーは、再試行する価値のあるものだけを再試行し、そうでないものでは停止します。重要な点は2つです。

  • 503 や接続切断の後に送信し直すときは、同じ Idempotency-Key を使うため、課金される2つ目のジョブが開始されることはありません。
  • ジョブが失敗した後にやり直すときは、新しいキーを使います。同じキーでは、失敗したジョブがそのまま返されるだけです。
Python
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 とは何を意味しますか?

送信した値(長さ、サイズ、参照画像の枚数など)をモデルが受け付けません。メッセージにフィールド名と受け付ける値が示されます。

失敗に課金しないAPIで開発する

すべての動画・画像モデルを1つのAPIキーで利用でき、ルート間の自動フォールバックにも対応。前払いクレジットは$10から、成功したジョブにのみ課金されます。