【Claude Code 連載 第52回】ゲートウェイの支出制限 — 共有請求書に開発者ごとのサーキットブレーカーを付ける

スポンサーリンク
【Claude Code 連載 第52回】ゲートウェイの支出制限 — 共有請求書に開発者ごとのサーキットブレーカーを付ける 用語解説
【Claude Code 連載 第52回】ゲートウェイの支出制限 — 共有請求書に開発者ごとのサーキットブレーカーを付ける
この記事は約17分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

スポンサーリンク

背景

この連載では、Claude Codeの公式ドキュメントを1ページずつ読み解いていきます。公式ドキュメントは情報が網羅されている分、「結局どの機能を、どんな場面で使えばいいのか」は自分で考える必要があり、読むのに意外と時間がかかります。そこで、私が実務で使うために読み込んだ内容を「使う場面→実例コード」の順に整理して残していくことにしました。専門家の解説というより、一次情報を読んだ記録です。推測や動作を確認していない部分には、その都度そう書きます。

1. 一言でいうと何か

支出制限は、各開発者がゲートウェイ経由で日/週/月あたりに使える金額に上限を設ける機能です。上限に達すると、ゲートウェイは次のリクエストで 429 を返し、期間がリセットされるか管理者が上限を引き上げるまでその開発者をブロックします。

なぜこの機能が必要になるのか。理由が一文で説明されていて、これがすべてです。

Claude アプリゲートウェイはすべての推論を「1つの共有アップストリーム認証情報」を通して転送するため、プロバイダーの請求書はすべてをその認証情報に紐づけて記録し、個々の開発者には記録しません。

つまり、ゲートウェイを導入した瞬間に**「誰がいくら使ったか」がプロバイダー側では見えなくなる**。認証情報を隔離するという設計上のメリットの裏返しです。そして開発者ごとの制限がなければ、1つの暴走したエージェントフリートが組織全体のコミットメントを使い果たしうる

支出制限は、その共有請求書の上に載せる**ゲートウェイ側の「開発者ごとのビュー」と「サーキットブレーカー」**です。この2語が機能の性格を正確に言い表しています——可視化と、緊急遮断。

そしてもうひとつ、最初に理解しておくべき性質があります。これは請求書ではありません。支出は USD リスト価格でのトークン数からの推定です。権威ある請求については、Anthropic の Usage & Cost Admin API、Amazon Bedrock の呼び出しログ、Google Cloud の Cloud Monitoring といったプロバイダー自身の使用状況レポートと突き合わせる必要があります。サーキットブレーカーとして十分な精度であって、経理の一次資料ではない、ということです。

有効化の方法も押さえておきます。gateway.yamladmin: ブロックを設定すると、ゲートウェイが /v1/organizations/spend_limits の管理 API を提供し、すべての推論リクエストで上限をリアルタイム適用します。**上限そのものは gateway.yaml ではなく、この API から設定します。**設定ファイルは機能のオン/オフとチューニング、キャップの値は API、という分担です。

2. どういう場面で役立つか

シーン1:業務委託メンバーに厳しめのキャップを敷く

グループ単位でキャップを設定できるので、contractors グループには1日100ドル、それ以外は組織デフォルトの月500ドル、といった出し分けができます。IdP グループがそのまま課金ポリシーの単位になります。

シーン2:暴走エージェントの被害を局所化する

/goal や自動モードで長時間走らせる運用(第46・47回)が広まるほど、設定ミス1つで大量のトークンを消費する事故は起きやすくなります。開発者ごとのキャップがあれば、被害はその1人分で止まります。組織全体のコミットメントが1晩で溶けることはない。

シーン3:誰が一番使っているかを把握する

/effective エンドポイントが、プリンシパルごと・期間ごとの解決済み上限と期間から現在までの支出を返します。sort=spend_desc で最大支出者から並べられるので、コスト管理の起点になります。

シーン4:既存の Admin API クライアントを流用する

API は Anthropic の公開 Admin API の支出制限エンドポイントのワイヤー形状をミラーリングしています。そのコントラクトに対して書かれた HTTP クライアントは、ベース URL を変えるだけでゲートウェイを叩けます

不要・向かないケース

  • 正確な請求・経理処理をしたい。前述のとおり推定です。プロバイダーのレポートと突き合わせてください。
  • 共有プールとしての予算管理をしたい。グループや組織の上限は**「各メンバーが継承する座席ごとのデフォルト」であって共有プールではありません**。「チーム全体で月1000ドルまで、誰が使ってもいい」という形は表現できません。
  • admin: ブロックを設定していない。API 自体が提供されず、enforcement の設定も効きません。
  • spend_limit_increase_requests のような他の Admin API サーフェスを使いたいゲートウェイが提供するのは支出制限エンドポイントだけです。増額申請のキューなどは含まれません。
  • Postgres を軽く扱いたい。支出制限を使うと、ホットパスが推論リクエストごとに Postgres を叩きます。第50回で触れた store.max_connections の引き上げや、第51回のバックアップ要否がここに効いてきます。

3. コマンドの実例と、適用の仕組み

上限を設定する

POST /v1/organizations/spend_limits{scope, amount, period} から1つの上限を作成または置き換えます。

組織全体のデフォルトとして月500ドルを設定する例:

curl -sS https://claude-gateway.internal.example.com/v1/organizations/spend_limits \
  -H "x-api-key: $GATEWAY_ADMIN_WRITE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"scope": {"type": "organization"}, "amount": "50000", "period": "monthly"}'

contractors グループの各メンバーに、より厳しい1日100ドルの上限を追加する例:

curl -sS https://claude-gateway.internal.example.com/v1/organizations/spend_limits \
  -H "x-api-key: $GATEWAY_ADMIN_WRITE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"scope": {"type": "rbac_group", "rbac_group_id": "contractors"}, "amount": "10000", "period": "daily"}'

amountUSD セントの整数文字列である点に注意してください。"50000" は 500 ドル、"10000" は 100 ドルです。

各フィールドの取りうる値は次のとおり。

  • scope.typeuser / rbac_group / organization の3種類。
    • userOIDC sub(IdP が割り当てる安定したユーザー ID)で1人を指定し、scope.user_id として渡します。
    • rbac_groupIdP グループを名前で指定し、scope.rbac_group_id として渡します。
    • organization は組織全体のデフォルト。
    • なお ゲートウェイは3つすべてを受け付けますが、Anthropic の公開 POST は現在ユーザーのみです。ワイヤー形状は同じでも、対応スコープはゲートウェイのほうが広い。
  • amount:USD セントの整数文字列、または nullnull は無制限、"0" はゼロ上限で全リクエストをブロックします。この2つの区別は事故のもとなので明確に。
  • perioddaily / weekly / monthly1つのスコープは期間ごとに1つの上限を持て、それぞれ独立に適用されます。開発者はいずれかの上限を超えていればブロックされます。

有効な上限の解決順序

複数のスコープが重なったとき、期間ごとに次の順で解決されます。

  1. ユーザーごとのオーバーライド
  2. 最も制限的なグループ上限
  3. 組織のデフォルト
  4. 無制限

複数グループに所属している場合のタイブレークはデフォルトで「最も制限的」ですが、admin.group_limit_mode: max を設定すると「最も制限的でない」方に反転します(第50回で触れた設定です)。

管理 API への認証

2つの方法があります。

  • x-api-key ヘッダーadmin.write_keys のキーならフルアクセス、admin.read_keys なら GET のみ。各キーは監査ログに admin-key:<id> として表示される id を持つので、Terraform、CI、各自動化にそれぞれ専用のキーを与えます
  • ゲートウェイのベアラートークンで、groups クレームが admin.admin_groups のいずれかを含むもの。フルアクセスで、監査には oidc:<sub> として記録されます。人間の管理者にはこちらが推奨

マシンには API キー、人間にはグループ。第50回と同じ使い分けです。

適用の仕組み

/v1/messages リクエストで、ゲートウェイは開発者の上限と期間から現在までの支出を、1つの Postgres クエリで解決します。超過していればリクエストは 429 を返し、error.type: billing_errorx-should-retry: false ヘッダーが付きます。メッセージは spend limit reached で、admin.blocked_message を設定していればその後に続きます(申請先の URL を書いておくと親切、という第50回の話がここで効きます)。

/v1/messages/count_tokens は除外されます。トークンカウントは無料なので、上限の状態に関係なく実行されます。

計測の仕組みも書かれています。各レスポンスの後、使用量メーターがレスポンスからトークンカウントを読み取り、USD リスト価格で価格付けし、3つの期間バケットすべての Postgres カウンターをインクリメントします。ここで設計上の配慮が2つ——メーターはストリーム上の単一リーダーなのでクライアントに届くバイトは変更されず、計測の障害はレスポンスを壊しません。課金の仕組みがユーザー体験を壊さないようになっています。

価格付けの2つの抜け穴対策

ここが読みどころです。素朴に実装すると開けてしまう穴を、2つとも塞いでいます。

穴1:認識されないモデル ID。価格テーブルは Claude Code CLI が自身のコスト表示に使うのと同じもので、Anthropic、Bedrock(us.anthropic.…-v1:0)、Agent Platform(claude-…@date)、Foundry の各 ID 形式にわたって同じ正規化を行います。しかし Foundry のデプロイメント名や推論プロファイル ARN のように、テーブルが解決できない ID が存在します。これを 0 円扱いにすると、認識されない ID を使うだけで上限を回避できてしまう。そこで不明なモデルは「100万入力/出力トークンあたり 5ドル/25ドル」のデフォルトティアで価格付けされます。ゲートウェイはブート時と実行時に ID ごとに1回、フォールバックで価格付けされたモデルについて警告を出すので、気づける仕組みにもなっています。

穴2:クライアント中止。アップストリームは出力トークン数をストリームの終端フレームでしか報告しないため、中止されたストリームにはその情報がありません。何もしなければ、上限に達した開発者が出力をストリーミングさせ、終了直前に毎回リクエストを中止することで、カウントされずに使い放題になります。対策として、メーターはストリーミングされたコンテンツのサイズから保守的なフロア推定値(トークンあたり約4文字)を保持し、終端の使用量フレームが欠落している場合にのみそれを課金します。完全なストリームでは常にアップストリーム報告のカウントを使うので、通常利用の精度は落ちません。

「悪用を前提に設計されている」ことがはっきり読み取れる箇所です。

Postgres が落ちたとき

事前チェックのクエリは 2秒のタイムアウトで Postgres に問い合わせます。到達できないかタイムアウトした場合、デフォルトはフェイルオープン——リクエストは進み、ゲートウェイは警告をログに出します。

enforcement.fail_closed_on_error: true を設定するとフェイルクローズになり、同じ 429 billing_error を返します(メッセージは spend limit unavailable)。

トレードオフは公式の言葉がそのまま答えです。**フェイルオープンはストア停止が推論停止になるのを防ぐ。フェイルクローズは計測されていない支出がないことを保証する。**第51回の readiness プローブの選択と同じ構造の判断です。

管理 API リファレンス

/v1/organizations/spend_limits の下に6つのエンドポイントがあります。

メソッドとパス説明
GET /v1/organizations/spend_limits設定された上限をリスト(?limit=&after_id=&before_id=
POST /v1/organizations/spend_limits{scope, period} の上限を作成または置き換え
GET /v1/organizations/spend_limits/{id}spl_ プレフィックス付き ID で1つ取得
DELETE /v1/organizations/spend_limits/{id}1つ削除。{type: "spend_limit_deleted", id} を返す
GET /v1/organizations/spend_limits/effectiveプリンシパルごと・期間ごとの解決済み上限と期間から現在までの支出
GET /v1/organizations/spend_limits/audit管理者の変更トレイル(最新順、?limit=

規約は Anthropic の Admin API をミラーします——すべてのオブジェクトに typespl_ プレフィックス付き IDUSD セントの整数文字列としての金額POST は他の currency400 で拒否)、{type: "error", error: {type, message}, request_id} のエラーエンベロープすべての管理レスポンスに本文の request_id と一致する request-id レスポンスヘッダー

監査の作り込みも堅実です。すべての変更は同じトランザクション内で admin_audit に変更前後の行を書きadmin-key:<id> または oidc:<sub> に属性付けされます。「変更は記録されたが実際の変更は失敗した」あるいはその逆、というズレが起きません。

/effective — ゲートウェイ固有の4つの違い

/effective は Anthropic の SpendSummary スキーマを返します。各行がプリンシパルの期間で、解決済み上限・期間から現在までの支出・actor オブジェクトを持ちます。ただしゲートウェイ固有の違いが4つあり、これはゲートウェイがユーザーディレクトリを持たないという設計から派生しています(第50回で触れた「SCIM がない理由」と同根です)。

  • user_id は OIDC sub
  • actor.nameactor.email_address は、そのプリンシパルの最初の推論リクエストがゲートウェイを通るまで null。ゲートウェイは各ユーザー自身のセッション JWT から最後に見た値を記録しているだけだからです。
  • 各行に groups 配列(最後に見た IdP グループ)が付く。これはゲートウェイ拡張で、管理 UI が「適用されるすべての上限ティア」を表示できるようにするためのもの。Anthropic 形状のクライアントは無視します。
  • user_ids[] フィルターがない場合、リストされるのは「記録された支出を持つプリンシパル」だけゲートウェイは全組織メンバーを列挙できないためです。

そして重要な一貫性の保証:グループ由来の上限は、適用が使うのと同じ group_limit_mode タイブレークで、そのユーザーの最後に見たグループに対して解決されるため、ビューアーに表示される上限は実際に適用される上限と一致します。「管理画面の表示と実際の挙動が違う」という古典的な事故を避けています。

クエリパラメータは5つ。user_ids[](繰り返し可、OIDC sub でフィルター)、period[](繰り返し可)、sortspend_desc は最大支出者から。正確に1つの period[] が必要)、q(OIDC sub・最後に見たメール・最後に見た表示名に対する大文字小文字を区別しない部分文字列フィルター)、limit / page(1〜1000、デフォルト20、page は前のレスポンスの next_page から得る不透明なカーソル)。

PII に関する警告があります。**q=user_ids[]= は GET のクエリ文字列に乗るため、前段のプロキシやロードバランサーがアクセスログに記録します。**PII のログポリシーが厳しい組織は、そこでこれらのパラメータをスクラブしてください。

ページネーションの2方式

生のリストは after_id / before_id(相互排他の spl_… ID)でページングし、結果は作成順、has_more はトラバース方向を反映します。

一方 /effective は不透明な ?page=(前のレスポンスの next_page トークン)でページングし、プリンシパルは昇順に並ぶため、支出が記録され続けている最中でもページが安定します。刻々と数字が動くビューでページがずれない、という配慮です。

limit はどちらも1〜1000、デフォルト20。

データライフサイクル

支出関連の4テーブルと、1時間ごとのスイープによる保持期間です(第51回の Postgres 節と重なりますが、こちらは支出に絞った視点)。

テーブル内容保持期間
spendプリンシパルごとの期間から現在までのカウンター(セント)admin.spend_retention_months(デフォルト13)
spend_limits設定された上限API 経由で削除されるまで
admin_audit変更トレイルadmin.audit_retention_days(デフォルト365)
principal_emails各プリンシパルの最後に見たメール・表示名・IdP グループ(PII を含むadmin.identity_retention_days(最終アクティビティ以降、デフォルト90)

identity_retention_daysspend_retention_months より意図的に短い理由が、ここでは明示されています——プロビジョニング解除されたアイデンティティは更新が止まって期限切れになり、その匿名の支出カウンターは年間比較レポート用に残る。個人情報は早く消え、集計は残る。よく考えられた分離です。

開発者が退職した場合の手順は2段階。まず DELETE /v1/organizations/spend_limits/{id} でユーザーごとの上限を削除し、支出とアイデンティティの行は上記の保持期間で自然に期限切れになります。オフボーディングや DSAR(データサブジェクトアクセスリクエスト)で即座に1人分を消す必要があるなら、ゲートウェイのデータベースに対して次を直接実行します。

DELETE FROM principal_emails WHERE principal = '<sub>'

メール・名前・グループを保持しているテーブルはこれだけで、spendadmin_audit の行は疑似匿名の OIDC sub しか参照せず、それぞれのウィンドウで期限切れになります。

4. まとめ + 次回予告

  • 支出制限は、共有アップストリーム認証情報という設計が生む「誰がいくら使ったか分からない」問題に対する、ゲートウェイ側の可視化+サーキットブレーカー
  • gateway.yamladmin: ブロックで機能を有効化し、キャップ自体は Admin API で設定する。
  • スコープは user(OIDC sub)/ rbac_group(グループ名)/ organization金額は USD セントの整数文字列null は無制限、"0" は全ブロック。期間は daily / weekly / monthly独立に適用され、どれか1つでも超えればブロック
  • **グループ・組織の上限は共有プールではなく座席ごとのデフォルト。**解決順は ユーザー → 最も制限的なグループ → 組織 → 無制限(group_limit_mode: max で反転)。
  • 超過時は 429error.type: billing_errorx-should-retry: falsecount_tokens は無料なので除外
  • これは請求書ではなく推定。USD リスト価格ベース。正確な数字はプロバイダーのレポートで照合する。
  • 抜け穴は2つとも塞がれている——不明なモデル ID は 5ドル/25ドル のデフォルトティアで課金(0円にしない)、中止されたストリームは約4文字/トークンのフロア推定で課金。
  • Postgres 到達不能時(2秒タイムアウト)はデフォルトでフェイルオープンenforcement.fail_closed_on_error: true で反転。「推論を止めない」か「未計測の支出を出さない」かの判断。
  • API は Anthropic Admin API のワイヤー形状をミラー。ベース URL を変えるだけで既存クライアントが使える。提供されるのは支出制限エンドポイントのみ。
  • /effective の表示は実際に適用される上限と一致する(同じタイブレークで解決)。ただし actor.name / actor.email_address は初回推論まで null、列挙対象は支出記録のあるプリンシパルのみ。
  • q=user_ids[]= はクエリ文字列に乗るので、前段プロキシのアクセスログで PII が漏れないようスクラブする。
  • **PII を持つのは principal_emails だけ。**DSAR 対応は1行の DELETE

第49〜52回でゲートウェイ関連は4本目。概要・設定・運用・課金と、管理者が必要とするものはひととおり揃いました。

次回予告(暫定):Google Cloud での完全な実装例(claude-apps-gateway-on-gcp)を取り上げ、Cloud Run / GKE・Cloud SQL・Secret Manager を使った具体的な構築手順を扱う予定です。

※連載の実際の次テーマは未確定です。


よっしー
よっしー

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。

それでは、また明日お会いしましょう(^^)

コメント

タイトルとURLをコピーしました