はじめに
OpenAI 互換 API を運用していると、HTTP status だけを見て判断したくなる場面があります。
特に 500 番台を見ると、私は反射的に「upstream が落ちているのでは」と思いがちです。ただ、今回あらためて確認したら、503 でも中身は model_not_found で、自分側の model 指定やルーティング設定を見るべきケースがありました。
この記事では、私が一次切り分けで見る HTTP status、error body、request ID、model、endpoint を表にします。サポートに投げる前の最小再現 curl も残します。
再現環境
今回の確認は 2026-07-06 09:32-09:34 JST に行いました。
| 項目 | 値 |
|---|---|
| endpoint | https://router.flatkey.ai/v1/chat/completions |
| model list | GET https://router.flatkey.ai/v1/models |
| 正常系 model | gemini-2.5-flash-lite |
| 異常系 model | not-a-real-model-issue-193 |
| 備考 | request ID は公開記事では一部伏せています |
/v1/models は HTTP 200 で、見えている model は 47 件でした。正常系の最小 chat は HTTP 200、usage.total_tokens=7 でした。
エラー全文
まず fake key です。これは素直に認証エラーでした。
{
"http_status": 401,
"error": {
"code": "",
"message": "Invalid token (request id: 20260706003333...redacted)",
"type": "new_api_error"
}
}
次に、実在しない model 名を指定したケースです。ここが今回のメモの主題です。HTTP status は 503 ですが、本文の code は model_not_found でした。
{
"http_status": 503,
"error": {
"code": "model_not_found",
"message": "分组 company-employees 下模型 not-a-real-model-issue-193 无可用渠道(distributor) (request id: 20260706003333...redacted)",
"type": "new_api_error"
}
}
この場合、私は 503 だけを見て retry 連打するより先に、同じ key で /v1/models に出ている model か、route や group が期待通りかを確認した方がよいと思いました。
再現手順
fake key の確認です。実運用では本物の key を貼らないようにします。
curl -sS -i "https://router.flatkey.ai/v1/chat/completions" \
-H "Authorization: Bearer sk-fk-local-fixture-invalid" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5.4-mini",
"messages": [{"role": "user", "content": "ping"}],
"max_tokens": 1
}'
同じ key で見えている model を確認します。
curl -sS "https://router.flatkey.ai/v1/models" \
-H "Authorization: Bearer $FLATKEY_API_KEY" \
| jq -r '.data[]?.id'
実在しない model を指定します。これは負荷をかけずに model 名のミスを確認するための最小リクエストです。
curl -sS -i "https://router.flatkey.ai/v1/chat/completions" \
-H "Authorization: Bearer $FLATKEY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "not-a-real-model-issue-193",
"messages": [{"role": "user", "content": "ping"}],
"max_tokens": 1
}'
正常系も 1 回だけ置いておくと、key、base URL、endpoint が全滅しているのか、特定 model だけが悪いのかを分けやすいです。
curl -sS -i "https://router.flatkey.ai/v1/chat/completions" \
-H "Authorization: Bearer $FLATKEY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gemini-2.5-flash-lite",
"messages": [{"role": "user", "content": "Reply with ok only."}],
"max_tokens": 3
}'
HTTP status だけで見ないための表
私の手元では、まずこの表で「自分の設定を見るもの」と「upstream や混雑を疑うもの」を分けます。
| status | まず疑うもの | retry 判断 | 集める情報 |
|---|---|---|---|
| 400 | JSON、schema、header、endpoint に合わない parameter | そのまま retry しない | request body、endpoint、SDK version、validation error |
| 401 | API key、organization、project、IP allowlist | retry しない | key の発行元、環境変数名、認証 header |
| 403 | region、権限、利用できない endpoint | retry しない | account/project 権限、利用地域、endpoint |
| 404 | endpoint path、resource ID、model 名 | retry しない | base URL、path、model、同じ key の /v1/models
|
| 408 | request timeout | 条件付きで retry | timeout 設定、処理時間、X-Client-Request-Id
|
| 429 rate limit | RPM/TPM、並列数、急な traffic 増 | backoff と jitter | rate-limit headers、並列数、queue 長 |
| 429 quota | credit、monthly spend、project budget | retry しない | billing、usage、limit、どの project の key か |
| 500 | provider/server 側の処理失敗 | 短い待ち時間後に上限付き retry | status page、request ID、最小再現 |
| 502/504 | gateway、network、upstream timeout | 上限付き retry | router request ID、upstream 名、timeout |
| 503 overloaded | upstream 混雑、Slow Down | backoff、rate を戻してから漸増 | traffic 推移、429/503 比率 |
503 model_not_found
|
model 名、route、group、catalog 差分 | retry 連打しない | model、同じ key の /v1/models、routing 設定 |
502/504 は今回わざと起こしていません。実障害を作るのはよくないので、ここは OpenAI 互換 gateway での一般的な local fixture として扱っています。
この表で大事にしているのは、retry できるかどうかを status だけで決めないことです。たとえば 429 は同じ status でも、短時間に投げすぎた rate limit と、残高や月次上限を超えた quota では対応が違います。前者は待つ、並列数を落とす、jitter を入れる、という話になります。後者は billing や project budget を直すまで同じ request を投げても進みません。
5xx も同じです。500 や overloaded の 503 なら、短い待ち時間を置いた上限付き retry と status page の確認が自然です。一方で、今回のように body に model_not_found が入っているなら、upstream 障害というより、自分の model 指定、利用可能 group、routing、catalog の差分を疑う方が早いです。私はここを混ぜて見てしまいがちなので、support に渡す前に body まで読むようにしています。
私の切り分け順
私はだいたい次の順で見ます。
-
statusだけでなくerror.code、error.type、error.messageを見る -
x-request-id、body 内の request ID、gateway 固有の request ID を保存する - 同じ key で
/v1/modelsを叩き、指定 model が本当に見えているか確認する - endpoint family を確認する。
/v1/chat/completionsと/v1/responsesを混ぜない - 401、403、404、400 は自分の設定ミスとして先に潰す
- 429 は rate limit と quota を分ける。quota は retry で直らない
- 5xx は body の code を見る。
model_not_foundのように設定寄りの 5xx もある - 同じ最小 curl で再現するかを確認してからサポートに渡す
OpenAI の rate limit docs でも、失敗した request は per-minute limit に加算されると説明されています。なので、429 や 503 を見た瞬間に無制限 retry するのは、私は避けた方がよいと思っています。
再試行より先に証跡を固定するだけで、あとから原因を追いやすくなります。
サポートに投げる前の最小セット
サポートへ投げる前に、私はこれだけ揃えます。
TRACE_ID="$(uuidgen | tr '[:upper:]' '[:lower:]')"
curl -sS -i "https://router.flatkey.ai/v1/chat/completions" \
-H "Authorization: Bearer $FLATKEY_API_KEY" \
-H "Content-Type: application/json" \
-H "X-Client-Request-Id: $TRACE_ID" \
-d '{
"model": "not-a-real-model-issue-193",
"messages": [{"role": "user", "content": "ping"}],
"max_tokens": 1
}'
共有する項目はこのくらいです。
| 項目 | 例 |
|---|---|
| status | 503 |
| request ID | 20260706003333...redacted |
| client request ID |
$TRACE_ID の値 |
| endpoint | /v1/chat/completions |
| model | not-a-real-model-issue-193 |
| body | error JSON 全体。key は絶対に含めない |
| 再現性 | 1 回だけか、複数回か、特定 model だけか |
OpenAI API reference では、x-request-id の保存と、必要に応じて X-Client-Request-Id を付ける方法が案内されています。timeout や network error で response header が取れないときも、自分で付けた client request ID があると調査しやすくなります。
気づき
今回一番の反省は、503 を見てすぐ upstream 障害と決めないことでした。
もちろん 500 や overloaded 系の 503 は provider 側の一時失敗として扱うことがあります。ただ、同じ 503 でも body が model_not_found なら、私の model 名、catalog、route、group 設定を見る方が先です。
逆に 401、403、404、400 は、たいてい自分の設定を直さないと進みません。retry よりも、key、base URL、endpoint、model、project 権限を紙に書くくらいの気持ちで揃えた方が早いかもしれません。
参考
- OpenAI docs: Error codes
- OpenAI docs: Rate limits
- OpenAI API reference: Debugging requests
- OpenAI docs: API request timeouts
おわりに
HTTP status は入口としては便利ですが、OpenAI 互換 API では gateway、model catalog、upstream provider、quota、rate limit が重なります。
私もしばらく 5xx なら upstream と雑に考えていたところがあるので、これからは status、request ID、model、endpoint、最小再現 curl を先に揃えてから判断しようと思います。
間違いあったらコメントください。よろしくお願いします。