はじめに
OpenAI 互換の router を使っていると、model ID を README や .env.example にそのまま書きたくなります。私もつい、pricing catalog に載っている名前を見て「これでよさそう」と思いがちです。
ただ、public catalog にある model、今の API key で /v1/models に出る model、実際に tiny request が通る model は別物でした。ここを混ぜると、あとで model_not_found を踏んだときに少し面倒です。
今回は Flatkey AI の OpenAI 互換 endpoint を例に、同じ日に catalog と /v1/models を取り、最後に小さい request を投げて、repo に残す model ID を絞ったメモです。
今日やったこと
やったことは地味です。
- public pricing catalog の model ID を取る
- authenticated
/v1/modelsの model ID を取る - 両方にある ID だけ候補にする
- 使う予定の ID だけ tiny request で確認する
- README や env には smoke 済みの ID だけ残す
OpenAI API reference でも /v1/models は利用可能な model の一覧と基本情報を見る endpoint です。OpenAI 互換 router でも、まずこの list を見ると話が早いと思います。
環境
確認日は 2026-06-25 です。
| 項目 | 値 |
|---|---|
| base URL | https://router.flatkey.ai/v1 |
| public catalog | https://router.flatkey.ai/api/pricing |
| authenticated model list | GET /v1/models |
| tiny success model | gemini-2.5-flash-lite |
| failure control model | Seedance2.0-fast |
API key は環境変数から読み、ログや記事には出していません。
public pricing catalog は全体カタログとして見る
まず public catalog を取りました。
curl -sS https://router.flatkey.ai/api/pricing
この日の結果はこうでした。
| 見たもの | 値 |
|---|---|
| HTTP status | 200 |
| catalog rows | 631 |
| unique model IDs | 631 |
| pricing_version | a42d372ccf0b5dd13ecf71203521f9d2 |
ここで大事なのは、これは全体カタログだということです。価格表や候補一覧としては便利ですが、今の key、group、endpoint で必ず使えるとは限りません。私はここを雑に扱うと、あとで README に古い model 名を残しがちです。
authenticated /v1/models は今の key の候補として見る
次に、同じ base URL で authenticated /v1/models を見ました。
curl -sS "$BASE_URL/models" \
-H "Authorization: Bearer $FLATKEY_API_KEY"
この日の結果は 48 IDs でした。public catalog は 631 IDs なので、数だけでもかなり違います。
差分はこうです。
| 比較 | 件数 |
|---|---|
catalog と /v1/models の両方にある ID |
46 |
catalog にはあるが /v1/models にはない ID |
585 |
/v1/models にはあるが catalog にはない ID |
2 |
この時点で、repo に残す候補は 631 件ではなく 46 件まで落ちました。ただし、この 46 件もまだ「候補」です。実行できるかは、最後に tiny request で見ます。
Python で積集合だけ出す
作業メモ用には、こんな小さい Python で十分でした。requests を入れず、標準ライブラリだけにしています。
import json
import os
import urllib.request
base_url = "https://router.flatkey.ai"
api_key = os.environ["FLATKEY_API_KEY"]
def get_json(path, auth=False):
headers = {}
if auth:
headers["Authorization"] = f"Bearer {api_key}"
req = urllib.request.Request(f"{base_url}{path}", headers=headers)
with urllib.request.urlopen(req, timeout=30) as res:
return json.load(res)
pricing = get_json("/api/pricing")
models = get_json("/v1/models", auth=True)
catalog_ids = {
row["model_name"]
for row in pricing["data"]
if row.get("model_name")
}
runtime_ids = {
row["id"]
for row in models["data"]
if row.get("id")
}
keep_candidates = sorted(catalog_ids & runtime_ids)
print("catalog", len(catalog_ids))
print("models", len(runtime_ids))
print("keep_candidates", len(keep_candidates))
print("\n".join(keep_candidates))
今回の出力は、catalog 631、models 48、keep_candidates 46 でした。
この script の出力をそのまま README に貼るのではなく、次の段階で「本当に使う数個」だけに絞ります。候補が多いまま残っていると、結局どれが動作確認済みなのか分からなくなるためです。
tiny request で最後に落とす
最後に、実際に使う model だけ tiny request を投げました。
curl -sS "$BASE_URL/chat/completions" \
-H "Authorization: Bearer $FLATKEY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gemini-2.5-flash-lite",
"messages": [
{ "role": "user", "content": "Return exactly: ok" }
],
"max_tokens": 4,
"temperature": 0
}'
結果は HTTP 200 でした。
{
"model": "gemini-2.5-flash-lite",
"first_content": "ok",
"usage": {
"prompt_tokens": 5,
"completion_tokens": 1,
"total_tokens": 6
}
}
同じ日に、catalog にはあるが /v1/models には出ていない Seedance2.0-fast も failure control として投げました。結果は HTTP 503 で、body は model_not_found でした。
{
"error": {
"message": "分组 company-employees 下模型 Seedance2.0-fast 无可用渠道(distributor) ...",
"type": "new_api_error",
"code": "model_not_found"
}
}
この比較で、少なくとも今回の README に Seedance2.0-fast を chat model として残すのはやめよう、と判断できます。catalog にある文字列でも、今の key と route で使えるとは限らないという証拠になります。
endpoint type と名前の揺れも見る
もうひとつ見落としやすいのが、同じように見える model 名の揺れです。今回の catalog と /v1/models には、Seedance 系だけでも複数の表記がありました。Seedance2.0-fast のような catalog 上の名前と、bytedance/seedance-2.0-fast のような slash 付きの名前は、文字列として別物です。
人間が見ると「たぶん同じ系列」と思えますが、API に渡す model は完全一致の文字列です。provider prefix の有無、日付 suffix、-fk や account 固有 suffix の有無を、雰囲気で補完しない方がよいと思います。
また、catalog 側に endpoint type が複数ある場合でも、自分が叩いている path と合っているかは別に見ます。Chat Completions に残すなら、少なくとも /v1/chat/completions で tiny request が通った ID を残したいです。動画、画像、embedding などの model ID を同じ CHAT_MODEL に混ぜると、後で読む人がかなり迷います。私はここを README の変数名で分けることにしました。
README や env に残す形
最終的に、私は README や .env.example には確認日と確認方法を一緒に残す方がよいと思いました。
# verified: 2026-06-25
# check: GET /v1/models + POST /v1/chat/completions tiny smoke
OPENAI_BASE_URL=https://router.flatkey.ai/v1
CHAT_MODEL=gemini-2.5-flash-lite
逆に、候補だったけれど smoke していない model は残しません。
# not kept:
# CHAT_MODEL=Seedance2.0-fast
このくらい素朴にしておくと、あとで model を差し替えるときも「いつ、何で確認したか」を追いやすいです。私の場合、model 名だけを残すと、数週間後に自分で理由を忘れます。
失敗ログも一緒に残す
成功ログだけだと、差分の意味が薄くなります。今回なら、次の 3 つをセットで残すのがちょうどよかったです。
| ログ | 残す理由 |
|---|---|
| public catalog count | 全体候補の母数を見る |
authenticated /v1/models count |
今の key で見える候補を見る |
| tiny success and failure | README に残す ID と落とす IDを説明する |
request id や社内情報が入る場合は伏せます。公開記事や issue に貼るなら、prompt も Return exactly: ok くらいにしておくと安心です。
まとめ
今回の確認で、public pricing catalog は 631 IDs、authenticated /v1/models は 48 IDs、積集合は 46 IDs でした。そこから実際に README に残す model は、tiny request で HTTP 200 になった gemini-2.5-flash-lite まで絞りました。
私の中では、OpenAI 互換 router の model ID は次の順番で扱うのがよさそうです。
- catalog は全体候補として見る
-
/v1/modelsは今の key の候補として見る - 積集合を作る
- repo に残す ID だけ tiny request で確認する
- 確認日と確認方法を README や env に添える
これなら、model_not_found が出たときも、catalog、権限、route、runtime のどこを疑うべきか切り分けやすいと思います。
おわりに
model catalog は便利ですが、catalog にある名前をそのまま production の env に入れるのは少し雑だったかもしれません。少なくとも私は、今回のように数を並べてみるまで差分の大きさを実感していませんでした。
同じように OpenAI 互換 router の model ID を固定している repo があれば、/v1/models と tiny request まで見てから残すと、後日の調査が楽になると思います。間違いあったらコメントください。よろしくお願いします。