Idempotency-Key で安全にリトライする
料金がかかるリクエストを送信した直後に、接続が切れたとします。Hyperouter はそのリクエストを受信したでしょうか。確かめる方法はありません。受信されていたのに再送すると、2 つ目のジョブが開始され、二重に支払うことになります。
Idempotency-Key ヘッダーを付ければ、この迷いはなくなります。Hyperouter は再送を識別し、2 つ目のジョブを開始せず、2 回目のクレジット確保も行わずに、最初のリクエストの結果で応答します。
料金がかかるすべてのリクエストに付ける
新しいリクエストごとに、UUID v4 などの新しいランダムな値を生成します。リクエストと一緒に保存しておき、そのリクエストを再送するたびに同じ値を送信してください。
curl https://api.hyperouter.app/v1/images/generations \
-H "Authorization: Bearer $HYPEROUTER_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7" \
-d '{"model": "x-ai/grok-imagine-image", "prompt": "A lighthouse at dusk", "n": 1}'
OpenAI SDK は、このヘッダーを自動では送信しません。追加のヘッダーとして渡してください。SDK は、自動リトライでもそれを変更せずに再送します。
import uuid key = str(uuid.uuid4()) # one per request; reuse it when you retry this request img = client.images.generate( model="x-ai/grok-imagine-image", prompt="A lighthouse at dusk", extra_headers={"Idempotency-Key": key}, )
import { randomUUID } from "node:crypto"; const key = randomUUID(); // one per request; reuse it when you retry this request const img = await client.images.generate( { model: "x-ai/grok-imagine-image", prompt: "A lighthouse at dusk" }, { headers: { "Idempotency-Key": key } }, );
エンドポイントごとの再送で返されるもの
| Endpoint | 再送で返されるもの |
|---|---|
POST /v1/images/generations、POST /v1/images/edits | 最初のリクエストの画像。Prefer: respond-async の場合は、同じ id を持つ 202。 |
POST /v1/videos、/v1/videos/edits、/v1/videos/extensions、/v1/videos/{id}/remix | 最初のリクエストの Video オブジェクト(現在の状態)。 |
POST /v1/chat/completions | 最初のリクエストの ID を伴う 409 idempotency_key_replayed。 |
チャットは異なります
チャットの応答は再送されません。同じキーで再送すると、コード idempotency_key_replayed の 409 が返り、error.generation_id が最初のリクエストの ID になります。リクエストは再実行も再課金もされません。
{
"error": {
"message": "A request with this Idempotency-Key already ran as generation 019a1f3c-5b7e-7d21-9c4a-2e8f6b0d3a17. ...",
"type": "conflict",
"code": "idempotency_key_replayed",
"param": null,
"generation_id": "019a1f3c-5b7e-7d21-9c4a-2e8f6b0d3a17"
}
}
最初のリクエストは GET /v1/generations/{id} で調べると、ステータス、利用量、課金額がわかります。最初のチャット補完が失敗して課金されなかった場合は、そのキーは解放され、再送は新しいリクエストとして実行されます。
つまりチャットでは、キーは二重課金を防ぐためのもので、同じ回答をもう一度取得する手段ではありません。409 は「すでに実行された」という意味と受け取り、記録を読み取ってください。
import os
import httpx
import openai
try:
res = client.chat.completions.create(
model="x-ai/grok-4.7",
messages=[{"role": "user", "content": "Hello"}],
extra_headers={"Idempotency-Key": key},
)
except openai.ConflictError as e:
body = e.body or {} # the error object
if body.get("code") != "idempotency_key_replayed":
raise
record = httpx.get(
f"https://api.hyperouter.app/v1/generations/{body['generation_id']}",
headers={"Authorization": f"Bearer {os.environ['HYPEROUTER_API_KEY']}"},
).json()["data"]
判定には message ではなく、常に code を使ってください。
再送で返されるもの
| 送信するもの | Hyperouter の応答 |
|---|---|
| 新しいキー | 通常どおりリクエストを実行します。 |
| 同じキーと同じリクエスト(最初のリクエストが受理された後) | 上の表のとおり、最初のリクエストの結果を返します。2 つ目のジョブも、2 回目のクレジット確保も、2 回目の課金もありません。 |
| 同じキーと同じリクエスト(最初のリクエストがまだ受理中) | 最初のリクエストを待ってから、上記のとおり応答します。最初のリクエストが 60 秒以内にジョブを開始しなかった場合は、409 idempotency_key_in_use が返ります。同じキーで再試行してください。 |
| 同じキーと異なるリクエスト | 409 idempotency_key_reused。何も実行されません。新しいキーを使ってください。 |
同じキー(最初のリクエストがジョブの開始前に拒否された後。400、402、または x-should-retry: false なしで送信された 5xx) | リクエストを新規として実行するため、修正して同じキーで再送できます。 |
| 同じキー(最初のリクエストから 30 分以上経過した後) | リクエストを新規として実行します。 |
| 255 文字を超える、または印字可能な ASCII ではないキー | 400 invalid_idempotency_key。何も実行されません。 |
ルール
- スコープ。キーは、それを送信した API キーに属します。2 つの API キーが同じ値を使っても、互いに影響しません。
- 有効期間。キーは、最初のリクエストから 30 分間有効です。その間に再試行してください。
- 同じリクエスト。メソッド、パス、ボディが一致する 2 つのリクエストは同じとみなされます。JSON は解析後に比較されるため、フィールドの順序や空白は影響しません。マルチパートのアップロードでは、ファイルのバイト列も比較されます。
- 課金。キーを付けて行ったリクエストは、再送の回数にかかわらず、最大で 1 回だけ課金されます。最初のリクエストの結果で応答した再送は無料です。
- 保証。1 つのキーで開始されるジョブは、2 つの再送が同時に届いた場合も含め、1 つを超えることはありません。
SDK の自動リトライ
OpenAI SDK は、408、409、429、5xx のレスポンスを、既定で 2 回再試行します。ただし、レスポンスにヘッダー x-should-retry: false が付いている場合は再試行しません。Hyperouter は、再試行すると 2 つ目のジョブや 2 回目の課金が発生するすべてのレスポンスに、このヘッダーを付けて送信します。対象は、同期画像の 503 timeout、ジョブの作成後の 500、409 idempotency_key_reused と idempotency_key_replayed です。
429、409 idempotency_key_in_use、ジョブが存在する前の 5xx には、このヘッダーを付けません。これらは安全に再試行できます。Idempotency-Key を付けていれば、すべての再試行が安全です。
詳しいルールは、リトライと冪等性に掲載しています。