LLM ゲートウェイ経由の Claude Code:誰が支払うかはクレデンシャルで決まり、クレデンシャルがなければプロキシに claude.ai のログイン情報が渡る
サブスクリプションでログインしていればリクエストごとに 12 個、ゲートウェイトークンなら 10 個の beta 値が付きます。消える 2 つの値は、ドキュメントの表よりも多くを語ります。ゲートウェイがそのまま通すべきもの、気づかないうちに高くつくデフォルト、そして allowedProviders で固定すると開発者が自分のデバッグ用リスナーまで使えなくなる理由を扱います。
目次
Claude Code を LLM ゲートウェイ経由で使うとき、何が起きるかは 2 つの環境変数で決まります。ANTHROPIC_BASE_URL はリクエストの送り先を決めます。ゲートウェイのクレデンシャル、つまり ANTHROPIC_AUTH_TOKEN、ANTHROPIC_API_KEY、apiKeyHelper のいずれかは、誰が支払うかを決めます。多くのセットアップガイドはこの 2 つをひとつの手順として扱いますが、実際には 2 つのモードです。ベース URL だけなら支払うのはサブスクリプションで、プロキシはリクエストのたびに claude.ai のログイン情報を受け取ります。これは個人が自分のマシンでデバッグするためのモードです。ゲートウェイのクレデンシャルを使えば組織がトークン単位で支払い、通信から 2 つの値が消えます。OAuth の beta と、1 時間キャッシュの beta です。チームで使うゲートウェイは 2 つ目のモードに属します。
通信上の違いを確かめるため、Claude Code 2.1.303 を localhost の小さなリスナーに向けました。リスナーは各リクエストを記録してエラーを返すだけなので、上流には何も届きません。実行は 3 回です。ベース URL のみ、ベース URL とダミーのゲートウェイトークン、そして 2 回目と同じ設定でゲートウェイ向けヒントヘッダーを有効にしたものです。以下のプロトコルの詳細はゲートウェイのドキュメントから、観察結果はこの 3 回のログから取っており、推奨はその両方を踏まえた筆者の解釈です。
Claude Code が LLM ゲートウェイと通信すると何が変わるか
ルーティングはどの場合も変わり、機能もひとつ変わります。ANTHROPIC_BASE_URL が Anthropic 以外のホストを指していると、claude.ai でサインインしていても Remote Control は無効になります。課金や利用制限などが変わるのは、ゲートウェイのクレデンシャルが有効なときだけです。ベース URL だけなら保存済みの claude.ai ログインがクレデンシャルのまま残るので、プランの利用制限と課金がそのまま適用され、ゲートウェイはログ付きのルーターにすぎません。
次の表は、claude.ai でサインイン済みで、ほかのクレデンシャルやプロバイダーを設定しておらず、キャッシュ設定がデフォルトのマシンを前提としています。
| ベース URL のみ | ベース URL + ゲートウェイのクレデンシャル | |
|---|---|---|
| 通信に乗るクレデンシャル | 保存済みの claude.ai ログイン | ゲートウェイのクレデンシャル |
| 課金 | 自分のサブスクリプション | トークン単位。ゲートウェイの背後にあるプロバイダーのクレデンシャルの所有者に請求 |
| サブスクリプションの利用制限 | 適用される | 適用されない |
| プロンプトキャッシュのデフォルト TTL(メインの会話) | 1 時間(プランの利用範囲内) | 5 分 |
| Remote Control | 無効 | 無効 |
| 音声入力 | ドキュメント上は利用可能(今回は未検証) | 無効 |
| claude.ai コネクター | 今回の実行では警告なし | 無効。起動時に警告あり |
コネクターの行はドキュメントではなく今回の実行結果です。ダミートークンを設定すると、claude -p は次のように表示しました。
claude.ai connectors are disabled because ANTHROPIC_API_KEY or another auth source is set and takes precedence over your claude.ai login
キャッシュの行は制限ではなくデフォルトです。プロンプトキャッシュのページによると、Claude Code が自動で 1 時間の TTL を要求するのは、Claude のサブスクリプションをプランの利用範囲内で使っている場合だけです。API キー、クラウドプロバイダー、利用クレジットでは、メインの会話は 5 分になります。設定の promptCacheTtl: "1h" か CLAUDE_CODE_PROMPT_CACHE_TTL=(Claude Code 2.1.242 以降)で 1 時間に戻せますが、その場合ゲートウェイは、1 時間の TTL を指定する beta 値を転送する必要があります。Anthropic 自身の Claude apps gateway は claude バイナリに同梱され、こちらもサブスクリプションのログインを無効にします。そしてこのゲートウェイは、1 時間の TTL をそもそも提供していません。デフォルトのままにすると、5 分を超える休憩を挟む長いセッションでは、戻るたびにキャッシュが書き直されます。
Claude Code を既存のゲートウェイに接続する方法
ゲートウェイの運用者からベース URL とクレデンシャルを受け取り、curl で確認してから 2 つの変数を設定します。管理者がすでにマネージド設定で配布していれば、claude はログイン画面なしで起動します。接続ページによると、そのとき /status には Anthropic base URL の行と、使われている変数名を示す Auth token または API key の行が表示されます。claude.ai アカウント名が入った Login method の行が出ていれば、クレデンシャルは届いていません。
クレデンシャルの変数は、ゲートウェイが読むヘッダーに合わせる必要があります。ANTHROPIC_AUTH_TOKEN は Authorization: Bearer として、ANTHROPIC_API_KEY は x-api-key として送られ、apiKeyHelper の値は両方に入ります。どれを使うか指示がなければ、まず Bearer トークンから試してください。ドキュメントにある確認方法は次のとおりです。
export ANTHROPIC_BASE_URL=https://llm-gateway.example.com
export ANTHROPIC_AUTH_TOKEN=sk-gateway-key
curl -sS -w '\n%{http_code}\n' -X POST "$ANTHROPIC_BASE_URL/v1/messages" \
-H "Authorization: Bearer $ANTHROPIC_AUTH_TOKEN" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{"model": "claude-sonnet-4-6", "max_tokens": 1, "messages": [{"role": "user", "content": "."}]}'401 はたいてい変数の誤りか失効したキーですが、ゲートウェイ自身のプロバイダー用クレデンシャルが上流で失敗している場合もあります。どちらかはゲートウェイのログで見分けられます。「unknown model」エラーが返れば、確認は成功です。ゲートウェイはモデル名を見る前に認証を通しています。
動作を確認できたら、2 つの値はシェルのプロファイルではなく ~/.claude/settings.json の env ブロックに移してください。シェルの export はバックグラウンドのエージェントに確実には届かず、同じ変数を両方で設定すると設定ファイルの値が優先されます。クレデンシャルをプロジェクトの .claude/settings.json に書いてはいけません。このファイルはコミットされます。VS Code 拡張は同じ 2 つを claudeCode. に求め、デスクトップアプリはどちらも無視して、独自のサードパーティ推論の設定を読みます。
Claude Code は LLM ゲートウェイに何を送るか
今回の実行では、まずクレデンシャルなしの HEAD /api/hello というウォームアップのプローブ(ドキュメントによると、HTTP プロキシかクライアント証明書を設定していると省略されます)、続いて POST /v1/ が届きました。セッション ID のヘッダー、anthropic-beta の完全なリスト、Messages API のボディ全体が付いています。以下は使ったリスナーを短くしたものです。ヘッダー名はそのまま残し、2 つのクレデンシャル用ヘッダーは先頭 7 文字と長さだけにし、モデル、ボディのトップレベルのキー、最初の system ブロックの冒頭を記録して、常に 503 を返します。
import json, sys
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
def redact(name, value):
if name.lower() in ("authorization", "x-api-key"):
token = value.split(" ")[-1]
return f"<redacted prefix={token[:7]} len={len(token)}>"
return value
class Handler(BaseHTTPRequestHandler):
def handle_any(self):
n = int(self.headers.get("content-length") or 0)
body = self.rfile.read(n) if n else b""
rec = {"method": self.command, "path": self.path,
"headers": {k: redact(k, v) for k, v in self.headers.items()}}
try:
j = json.loads(body)
rec["model"] = j.get("model")
rec["body_keys"] = sorted(j)
rec["system0"] = (j.get("system") or [{}])[0].get("text", "")[:60]
except (ValueError, AttributeError):
pass
with open("requests.jsonl", "a") as f:
f.write(json.dumps(rec) + "\n")
self.send_response(503)
self.end_headers()
do_GET = do_POST = do_HEAD = handle_any
def log_message(self, *args):
pass
ThreadingHTTPServer(("127.0.0.1", int(sys.argv[1])), Handler).serve_forever()伏せるのはこの 2 つのヘッダーだけなので、ANTHROPIC_CUSTOM_HEADERS に秘密情報を入れていると、それはそのままファイルに残ります。リスナーを python3 listen.py 8765 で起動し、設定ファイルのどの env ブロックもベース URL やクレデンシャルを設定していないことを確認してください(設定ファイルの値は export より優先され、リクエストが本物のゲートウェイに送られてしまいます)。そのうえで別のターミナルで次を実行します。
ANTHROPIC_BASE_URL=http://127.0.0.1:8765 timeout 60 claude -p "say ok" --model haiku < /dev/null2 回目は同じ行に ANTHROPIC_AUTH_TOKEN= を、3 回目はさらに CLAUDE_CODE_GATEWAY_HINT_HEADERS= を加えます。Claude Code は 503 をリトライするので、各実行で同じリクエストが何件か記録されますが、見るのは 1 件で十分です。ベース URL のみの実行で最初に届いたモデルへのリクエストを短くしたものです。
POST /v1/messages?beta=true model claude-haiku-5-5
Authorization: Bearer <redacted prefix=sk-ant- len=108>
User-Agent: claude-cli/2.1.303 (external, sdk-cli)
X-Claude-Code-Session-Id: f85023e8-...
anthropic-version: 2023-06-01
anthropic-beta: oauth-2025-04-20, interleaved-thinking-2025-05-14,
thinking-token-count-2026-05-13, context-management-2025-06-27,
prompt-caching-scope-2026-01-05, claude-code-20250219,
per-turn-control-2026-07-01, mid-conversation-tool-changes-2026-07-01,
inline-tools-2026-09-15, advisor-tool-2026-03-01, effort-2025-11-24,
extended-cache-ttl-2025-04-11
body keys: context_management, max_tokens, messages, metadata, model,
output_config, stream, system, thinking, tools
system[0]: x-anthropic-billing-header: cc_version=2.1.303...ダミーのゲートウェイトークンを設定すると、同じリクエストの beta 値は 12 個ではなく 10 個でした。oauth-2025-04-20 が消えたのは想定どおりで、extended-cache-ttl-2025-04-11 も消えていました。これは、デフォルトが 1 時間から 5 分に切り替わるというドキュメントの記述と一致します。リスナーは本物の応答を一度も返していないので、ここでわかるのはリクエストの中身であり、実際に何がキャッシュされ課金されたかではありません。
ドキュメントにあるエージェント単位の帰属ヘッダー、x-claude-code-agent-id と、ネストしたエージェントでの x-claude-code-parent-agent-id は、サブエージェントからのリクエストにしか付きません。今回の実行ではサブエージェントは起動していません。ルーティング用のヒントは、カスタムのベース URL の背後ではデフォルトで無効です。未知のヘッダーを拒否するプロキシだとリクエストが失敗するからです。CLAUDE_CODE_GATEWAY_HINT_HEADERS=(ヒントには Claude Code 2.1.273 以降、プロンプト ID には 2.1.283 以降が必要)を設定した 3 回目の実行では、x-claude-code-request-class: main と x-claude-code-prompt-id が追加されました。ドキュメントにはさらに 4 つ、エージェントの種類、コンパクション、ツールの所要時間に関するものが載っており、それらは該当する出来事があったときだけ付きます。どれにもプロンプトの本文は含まれません。
最初の system エントリーは帰属ブロックです。api.anthropic.com の Claude API は、これが最初の system ブロックとして変更されずに届けば取り除きます。それ以外の上流には、プロンプトの本文として届きます。system 配列を並べ替えたり、文字列に平坦化したりするゲートウェイは、このブロックをプロンプトとキャッシュキーに押し込みます。最初のエントリーにほかの system テキストを結合するゲートウェイはもっと悪く、API は結合後のエントリーを帰属情報とみなして丸ごと捨てます。ゲートウェイが system の内容を組み替えざるをえないなら、代わりにクライアント側で CLAUDE_CODE_ATTRIBUTION_HEADER= を設定してください。
Claude Code のゲートウェイは claude.ai のログイン情報を見られるか
ベース URL のみのモードなら、見られます。1 回目の実行でモデルへの各リクエストに付いていた Bearer トークンは、ゲートウェイのキーではありませんでした。マシンに API キーがない以上、それは保存済みの claude.ai ログインです。だから oauth-2025-04-20 の beta が一緒に送られ、ドキュメントもゲートウェイ運用者にこの値を転送するよう求めています。転送しなければ上流は 401 を返します。プロキシの運用者は、リクエストのたびに利用者のサブスクリプションのクレデンシャルを受け取ることになります。
自分のマシンで、上のように何も転送しないリスナーを使うなら、他人のアカウントに触れずに自分のセッションが何を送っているかを確認する方法になります。チームの設計としてはまったく別物です。このモードの共有ゲートウェイは、開発者全員の個人ログインを集め、組織のトラフィックを個人のコンシューマープランで処理することになります。11 月 12 日に発効する利用ポリシーは、アクセスを提供する不正な方法のひとつとして「services that route requests through consumer subscriptions」を挙げています。これについては利用ポリシーの比較で詳しく見ました。誰が呼び出しているかを把握し、予算を割り当て、監査ログを残すゲートウェイには独自のクレデンシャルが必要で、組織アカウントへのトークン単位の課金はそれに伴って付いてきます。
このモードには請求額以外のコストもあります。Slack 上の Claude Code やクラウドセッションはゲートウェイの対象外なので、トラフィックをゲートウェイに限定するなら、該当するユーザーではそれらを無効にする必要があります。ゲートウェイがルーティングするのはモデルへのリクエストだけです。バージョン確認、テレメトリー、高速モードの確認、WebFetch のドメイン確認は、ゲートウェイを通らずに別の宛先へ送られ続けます。これを避けるには、それらを無効にするか、外部への通信として許可しておく必要があります。そしてゲートウェイは、Claude Code のリリースのたびに追従して保守するソフトウェアになります。
制御機能のほかに得られるのは、より良いコストの単位です。x-claude-code-session-id を使えば、ボディを解析せずにセッションのリクエストをまとめられ、エージェント ID のヘッダーはサブエージェントを起動するセッションを部分ごとに分けられます。開発者ごとの合計では誰が使ったかしかわかりませんが、これならどんな種類のタスクが高くつくかがわかります。なおドキュメントは、エージェント ID が識別するのはエージェントであって人ではないと注意しています。
Claude Code の機能を動かすために LLM ゲートウェイが転送すべきもの
Anthropic 形式の上流に向けては、anthropic- で始まるすべてのヘッダー、変更していないリクエストボディ、届いたままのストリームを転送します。プロトコルのページは、これらを開かれたリストと呼んでいます。Claude Code はリリースのたびに anthropic-beta の値やボディのフィールドを追加するので、今見えているものだけを許可リストに入れるゲートウェイは、新しい機能が出るたびに、そのリリース当日に壊してしまいます。Anthropic 形式のリクエストを受けて Amazon Bedrock や Google Cloud に転送するゲートウェイは逆の役割で、スキーマを変換するか、代わりにそのプロバイダー自身の形式をクライアントに公開します。転送が中途半端な機能は、それぞれ違う壊れ方をします。
- beta とボディの組み合わせ。 beta ヘッダーを削ってそのボディのフィールドだけを通すと、上流は
400を返し、多くの場合Extra inputs are not permittedとなります。コンテキスト管理、beta ツールのフィールド、output_configはどれもこの組み合わせです。両方が欠けたときだけ、機能は黙って無効になります。 - プロンプトキャッシュ。
cache_controlマーカーを落としたり動かしたり、ブロック形式のコンテンツを文字列に変えたりしても、エラーは出ません。毎ターンがキャッシュされない入力として課金されます。この中で最も気づきにくい問題で、プロンプトキャッシュを壊した原因を探すで紹介した診断を使えば見つけられます。 - ストリーミング。 応答をバッファリングするゲートウェイでは Claude Code が止まったように見え、SSE の
pingイベントを削るゲートウェイでは、長い思考の間にアイドルタイムアウトが発生することがあります。 - レスポンスヘッダー。
anthropic-ratelimit-unified-*とx-should-retryはそのまま転送し、retry-afterは秒単位で返し、エラーのボディは手を加えずに通してください。Claude Code は、拒否された機能からの回復を上流の文言で判断します。レート制限のヘッダーはプランの利用状況の表示にも使われ、ステータスラインのrate_limitsフィールドと同じ数値の元になっています。
ゲートウェイのせいではない失敗もひとつあります。ゲートウェイの手前に置いた Web アプリケーションファイアウォールが、HTML ボディ付きの 403 を返し、ゲートウェイには何も記録されないことがあります。Claude Code のプロンプトには XML 風のタグやソースコードが含まれ、クロスサイトスクリプティング対策のボディルールに引っかかるからです。短い curl のテストは通るのに、実際のセッションは失敗します。/v1/messages はボディ検査の対象から外してください。
すべてを転送できない場合は、CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS= でほとんどのプレリリース機能を止められます。ただし互換性の保証ではありません。OAuth の値、拡張コンテキスト、インターリーブ思考、effort、適応型の thinking フィールドは残り、開発者が ANTHROPIC_BETAS や CLAUDE_CODE_EXTRA_BODY で追加したものも残ります。
allowedProviders で Claude Code をひとつのゲートウェイに固定する仕組み
allowedProviders は、ほかの宛先を指すセッションをすべて拒否します。マネージド設定で allowedProviders を ["customEndpoint"] にし、同じファイルの env ブロックでゲートウェイのアドレスを固定します。すると Claude Code は ANTHROPIC_BASE_URL をその値ちょうどのときだけ受け入れ、Anthropic への直接接続、クラウドプロバイダー、開発者自身のプロキシを拒否します。拒否は起動時、ログイン時、そしてセッション途中で何かが変わった場合は次の API 呼び出し時に行われます。Claude Code 2.1.285 以降が必要です。Linux ではファイルは /etc/ で、そこで指定するヘルパーは、管理者がインストールし、開発者のゲートウェイ用クレデンシャルだけを出力するスクリプトです。
{
"allowedProviders": ["customEndpoint"],
"env": {
"ANTHROPIC_BASE_URL": "https://llm-gateway.example.com"
},
"apiKeyHelper": "/usr/local/bin/get-gateway-key"
}マネージド設定の ANTHROPIC_BASE_URL は、それだけで開発者のシェルの export より優先されます。allowedProviders が加えるのは、ほかの扉を閉じることです。CLAUDE_CODE_USE_BEDROCK のようなプロバイダー変数、直接のサインイン、2 つ目のベース URL がそれにあたります。これは A prompt is not an invariant で論じた違いそのもので、ルールはハーネスが強制する場所に置かれます。ゲートウェイのクレデンシャルを配布するときは、forceLoginMethod、forceLoginOrgUUID、forceLoginGatewayUrl をマネージド設定に入れないでください。どれかひとつでも起動時にクレデンシャルをブロックし、開発者は先に進めなくなります。
固定するかどうかはガバナンスの問題ですが、固定にはドキュメントが明言していないコストがあります。固定したマシンは試していませんが、設定リファレンスによれば、上の localhost のリスナーはベース URL を変えるので、そこでは拒否されます。開発者は、セッションが何を送っているかを見るためにエンドポイントをリスナーに差し替えることができなくなります。代わりにゲートウェイのログでデバッグさせるなら、そこで自分のセッションを読めるアクセス権を与えてください。
Claude Code のゲートウェイを展開する前に確認すること
最初にクレデンシャルのモードを決めてください。それで請求先が決まります。個人が自分でデバッグするならベース URL のみ、共有するものならゲートウェイのクレデンシャルです。次に、デフォルトの 5 分に任せず、キャッシュの TTL を意識して決めます。curl だけでなく実際のセッションをゲートウェイに通し、/status、最初の長い応答がストリーミングを始めるまでの速さ、数ターンにわたるキャッシュ読み取りの数値を確認します。一度 Claude Code を記録用のリスナーに向け、送られてくる十数個の anthropic-beta の値を読んでください。ゲートウェイはそのすべてと、次のリリースで加わる値を通す必要があります。
01 / ディスカッション
コメント欄はありません。議論は X で行っています。
02 / ほかの記事
claude -p での Claude Code フォークサブエージェント:2.1.302 がヘッドレス実行で変えたこと
提供されることと使われることは別です。ヘッドレス実行のモデルはフォークを一度も選ばないかもしれず、公式ドキュメントはまだ古いデフォルトを説明しています。パイプラインがどのサブエージェントを使ったかの確認方法、バックグラウンドのフォークがパーサーに2つの回答を渡す理由、そして無効にする価値がある場面を解説します。
Claude Code のステータスラインと ccusage:ネイティブのレート制限と、別の時刻に終わる 5 時間ブロック
2 つのツールが同じ問いに別の根拠で答えます。サーバーはリセット時刻を報告し、ログパーサーはそれを推定します。数時間ずれたらサーバーを信じ、パーサーは履歴用に残してください。サブスクリプションでは、ドル金額は為替レートのようなものです。
Claude Code vs Cursor: どちらのツールも 1 つの利用環境に縛られなくなった今、開発者が仕事を分ける 7 つの方法
エディタ対ターミナルという古い境界線は、両ベンダーがそれぞれの側から消しました。それでも 2 つを分けているものは、もっと目立たないところにあります。Cursor は Claude Code の設定フォルダを毎回そのまま読み、Claude Code は Cursor のルールを一度だけ取り込みます。そして権限の仕組みは、互いの deny ルールを無視します。