ナビゲーションにスキップ

開発ガイド

開発ガイド

クイックガイドでは最初のリクエストを動かします。このページでは、本番で継続して動作させるために必要な対応を説明します。

2 種類の呼び出し

BeatAPI のリクエストには 2 つのライフサイクルがあります。連携時の多くの問題は、両者を混同することで起こります。

テキストワークフロー、画像、動画、エフェクト
返されるもの回答タスク ID
待ち時間数秒数秒から数分
ストリーミングServer-Sent Events対象外
完了の確認HTTP レスポンスポーリング、または Webhook
課金レスポンス時精算時。失敗時は返金

テキストは同期です。POST /v1/chat/completions、/v1/responses、/v1/messages、Gemini 互換エンドポイントは結果を返します。それ以外は処理を受け付け、ID を返します。

非同期のライフサイクル

  1. 送信します。 POST /v1/images/tasks(または videos、effects、video-analysis、music-video、ecommerce-video)を呼びます。201 は受け付けを意味し、完了ではありません。
  2. ID を保存します。 バックグラウンド処理の開始前に、data.id と data.request_id を永続化します。レスポンスから保存までの間にクラッシュすると、支払い済みなのに追跡できないタスクが残ります。
  3. 待ちます。 GET /v1/tasks/{task_id} をポーリングするか、Webhook を登録します。
  4. 結果を読み取ります。 succeeded では data.output.media を使い、failed では data.error_code と返金フィールドを確認します。

ポーリング

import time
import random
import requests
TERMINAL = {"succeeded", "failed"}
def wait_for_task(api_key, task_id, timeout=900):
url = f"https://api.beatapi.io/v1/tasks/{task_id}"
headers = {"Authorization": f"Bearer {api_key}"}
deadline = time.time() + timeout
delay = 5.0
while time.time() < deadline:
response = requests.get(url, headers=headers, timeout=30)
if response.status_code == 429:
time.sleep(float(response.headers.get("Retry-After", 10)))
continue
response.raise_for_status()
task = response.json()["data"]
if task["status"] in TERMINAL:
return task
# ジッターで複数ワーカーの確認タイミングを分散します。
time.sleep(delay + random.uniform(0, delay * 0.3))
delay = min(delay * 1.5, 30.0)
raise TimeoutError(f"task {task_id} did not finish within {timeout}s")

このループでは次の 3 点が重要です。

  • 終了状態で停止します。 経過時間から完了を推測しません。完了したタスクは以後変化しません。
  • ジッター付きのバックオフを使います。 タスクエンドポイントの上限はキーごとに毎分 120 リクエストです。複数のタスクを 1 秒ごとに確認すると、この上限を使い切る場合があります。
  • 429 は失敗ではなく待機として扱います。 Retry-After に従ってください。

Music Video の storyboard_ready と requires_action は、ポーリングではなく判断や操作が必要な未完了状態です。処理中として扱うと永遠にポーリングしてしまいます。全状態と必要な操作はクイックガイドを参照してください。

ポーリングの代わりに Webhook を使う

POST /v1/webhooks でエンドポイントを登録すると、BeatAPI が終了状態を通知します。到達可能な HTTPS エンドポイントが必要ですが、ポーリングループを省けます。

JSON を解析する前に、生のリクエストバイト列で署名を検証してください。本文を再シリアライズすると署名が一致しません。Webhooks を参照してください。

Webhook を設定しても、低頻度の照合用ポーリングは残してください。通知の配信に失敗する場合があります。モデルの通常の所要時間を大きく超えて processing のままなら、直接確認する価値があります。

エラー

失敗時には、安定した error.code、原因を示す英語の message、retryable、request_id が返ります。サポートで対象の呼び出しを特定できるよう、request_id をログに記録してください。

{
"error": {
"code": "rate_limit_exceeded",
"message": "Too many requests. Wait 12 seconds, then retry.",
"retryable": true,
"request_id": "req_abc123",
"retry_after_seconds": 12
}
}
HTTP再試行意味
400不可リクエストが不正。再試行しても同じ不正な内容を送る
401不可missing_api_key / invalid_api_key。キーが未指定、不正、期限切れ、無効、上限到達
402不可insufficient_credits。残高が 0、または料金に不足。チャージが必要
403不可forbidden。キーまたはアカウントでは操作できない
404不可not_found。不明なモデル、不正な ID、または他アカウントの ID
409不可idempotency_conflict。同じ冪等キーを異なる本文で使った
429Retry-After 後リクエスト数または同時実行数の上限
500、503バックオフして可一時的な問題。既存タスクは再送せずポーリング

retryable はコードごとに同じ判断を示すため、ステータスの代わりに使えます。BeatAPI 自身は 502 と 504 を返しません。届いた場合はネットワークのエッジで発生したもので、503 と同様に再試行します。全コードはエラーに掲載しています。

import requests
response = requests.post(
"https://api.beatapi.io/v1/images/tasks",
headers={"Authorization": f"Bearer {api_key}"},
json={"model": "nano-banana", "prompt": "a cute panda"},
timeout=30,
)
if not response.ok:
error = response.json().get("error", {})
# 後からこの呼び出しを特定するために request_id を保存します。
log.error("beatapi failed", code=error.get("code"), request_id=error.get("request_id"))
if response.status_code == 402:
alert_billing()
elif response.status_code == 429:
schedule_retry(after=error.get("retry_after_seconds", 10))

送信時の 500 だけでは、タスクが作成されたか判断できません。無条件で再送すると、同じ処理に二重で支払う可能性があります。作成時に Idempotency-Key を送信し、再試行でも同じ値を使ってください。BeatAPI は別のタスクを作らず、元のタスクを返します。

レート制限

独立した 2 つの制限があり、対処方法が異なります。

  • リクエスト数はキーごとの毎分の上限で、Retry-After を伴う 429 を返します。チャージ累計額に応じて増えます。現在の値と次の段階はダッシュボードと GET /api/user/self で確認できます。
  • 同時実行数は処理中のタスク数の上限で、user_concurrency_exceeded を返します。実行中のタスクの完了を待つ必要があります。即座の再試行はリクエスト数を消費するだけです。

GET /v1/usage は concurrency.limit と concurrency.active を返します。

二重課金を避ける再試行

  • 429 と 5xx(BeatAPI の 5xx は 500 または 503)はバックオフして再試行します。400、401、402、403、404、409 は retryable: false のため再試行しません。
  • ジッターと上限を設けた指数バックオフを使います。
  • タスク作成には毎回 Idempotency-Key を付け、再送による二重課金を防ぎます。
  • 送信が失敗しても ID があれば、再送する前にポーリングします。
  • 試行回数に上限を設けます。永続的な失敗を際限なく再試行すると、費用を伴う障害になります。

コスト管理

  • テキスト出力に上限を設けます。 出力側の料金が高いため、max_tokens が最も簡単な方法です。
  • 処理ごとにモデルを選びます。 分類やルーティングに最上位モデルは不要です。
  • 段階料金のモデルを把握します。 GPT-5.6 と Grok は所定のコンテキスト長を超えると単価が上がります。2 つの DeepSeek HC(高キャッシュ)モデルは平日の北京時間 09:00–12:00 と 14:00–18:00、Hunyuan hy3 は公開された独自のピーク時間帯で高くなります。通常の有料 DeepSeek モデルは終日同じ料金です。長いエージェント処理や定期バッチでは、気付かないまま条件を超える場合があります。各レスポンスの利用記録で適用料金を確認できます。
  • コンポーネントごとにキーを分けます。 GET /v1/usage の by_api_key で独自の計測なしに利用額を区別できます。
  • 返金を照合します。 失敗したタスクは返金されます。仮確保だけを記録すると請求額と差が生じます。

本番利用の前に

  • 接続先は正確に https://api.beatapi.io を指定します。
  • 長期利用するキーはサーバー側に保存します。ブラウザーには API キーではなく、短寿命の Realtime client_secret を渡します。
  • バックグラウンド処理を始める前に data.id と request_id を保存します。
  • ポーリングにジッターを付け、終了状態や操作待ち状態で止めます。
  • 生のバイト列で Webhook の署名を検証します。
  • 仮確保、精算、返金、エラー、最終出力 URL を記録します。
  • アカウントの有料操作が一斉に止まる 402 を監視します。