Claude Media
Claude Analytics APIでユーザー別コストを取得する方法

Claude Analytics APIでユーザー別コストを取得する方法

Claude Enterprise Analytics APIのuser_cost_reportは席ユーザー起因のコストだけを支出順に返す。組織合計のcost_reportとの違い、必要なスコープ、group_byの使い方をまとめる。

Claude Enterprise Analytics APIには、コストを返すエンドポイントが2本あります。ユーザーごとの支出を順位付きで返す user_cost_report と、時間バケットで組織全体を返す cost_report です。名前は似ていますが、数字の母集団が違います。メンバー別の請求配賦に使うなら前者、経理の締めに使うなら後者、という分け方になります。

user_cost_reportが返すのは「席ユーザー起因のコスト」だけ

user_cost_report は GET /v1/organizations/analytics/user_cost_report で呼ぶエンドポイントです。指定した期間のコストを米ドルで集計し、ユーザー1人につき1行で、支出の多い順に返します。「誰がいちばん使っているか」を知るためのAPIです。

重要なのは母集団です。公式の説明では、含まれるのは席ユーザーに帰属するコストだけです。APIキーを直接叩いたトラフィックや自動化のコストは、この一覧に入りません。したがって、全ユーザー行の amount を足しても組織の総額にはなりません。

組織合計が必要なときは、バケット型の /v1/organizations/analytics/cost_report を使います。公式もそう案内しています。

2つのエンドポイントの違い

観点user_cost_reportcost_report
パスuser_cost_report/v1/organizations/analytics/user_cost_reportcost_report/v1/organizations/analytics/cost_report
返し方user_cost_reportユーザー1人につき1行、支出順cost_report時間バケットごと、古い順
母集団user_cost_report席ユーザーに帰属するコストのみcost_report組織合計(APIキー直叩き・自動化を含む)
bucket_widthuser_cost_report任意。指定すると行が時間で分割されるcost_report既定は 1d
limit の単位user_cost_report行数(1〜1000、既定20)cost_report時間バケット数(1d は既定7・最大31)
主な用途user_cost_report高額利用者の特定、メンバー別の配賦cost_report日次・時間別の推移、総額の把握

limit の意味が違う点は見落としやすい箇所です。user_cost_report では1ページあたりの行数を指し、cost_report では時間バケットの数を指します。cost_report は bucket_width ごとに既定値と上限が異なり、1h なら既定24・最大168、1m なら既定60・最大256です。

利用条件と認証

どちらのエンドポイントも、利用できるのはClaude Enterpriseプランの組織です。呼び出しには read:analytics スコープを持つAPIキーが必要で、このキーはclaude.aiのOrganization settingsにあるAPIのページで公開APIアクセスを有効にしてから、Analytics APIキーとして作成します。Claude ConsoleのAdmin APIキー(sk-ant-admin01-...)とは別物で、互換性はありません。2種類のキーの使い分けはClaude Analytics API群の使い分けに整理してあります。

プランの種類にも条件があります。コスト系のエンドポイントは使用量ベースのEnterpriseプラン向けで、シート課金型のプランでは、返る数字が利用クレジット(usage credits)の分だけになります。シート課金型の組織で合計が請求額より小さく見えても、APIの不具合とは限りません。

Claude Console側のAPI利用コストを取りたい場合は、こちらの系統ではありません。Usage and Cost APIを扱ったClaude Usage APIとCost APIで利用量とコストを取得するが別の入口です。

最小のリクエスト

必須のクエリパラメータは starting_at だけです。RFC 3339のタイムゾーン付き日時で指定し、直近365日以内、かつ2026年1月1日(UTC)以降でなければなりません。ending_at を省くと、現在時刻と starting_at に31日を足した日時のうち早いほうが使われます。範囲は最大31日です。

curl "https://api.anthropic.com/v1/organizations/analytics/user_cost_report\
?starting_at=2026-09-01T00:00:00Z\
&ending_at=2026-09-15T00:00:00Z\
&limit=100" \
  --header "x-api-key: $ANALYTICS_API_KEY" \
  --header "anthropic-version: 2023-06-01"

anthropic-version ヘッダーは毎回必要です。応答は data の配列で、各行に actor(ユーザー情報)、amount、list_amount、currency、requests などが入ります。group_by[] も bucket_width も付けない場合、ユーザー1人分の行は次のような形です(値は例)。

{
  "data": [
    {
      "actor": {
        "type": "user_actor",
        "user_id": "user_01AbCdEfGhIjKlMnOpQrSt",
        "email": "jane@example.com",
        "name": "Jane Smith",
        "deleted": false
      },
      "amount": "41280.000000",
      "list_amount": "51600.000000",
      "currency": "USD",
      "requests": 128
    }
  ],
  "data_refreshed_at": "2026-09-15T04:00:00.000Z",
  "has_more": true,
  "next_page": "<opaque cursor>",
  "organization_id": "org_013FP9SaFPBg7Kw7fetjn6cF"
}

group_by[] で分けた次元の値(model や product など)は、その行に追加のフィールドとして現れます。has_more がtrueなら、次のページが残っています。

金額は「セント単位の10進文字列」で返る

amount は割引後・クレジット適用前の金額で、単位は小数を含むセントです。たとえば "41280.000000" は412.80ドルにあたります。ドルに直すには、10進数として解釈して100で割ります。金額が数百万ドルを超える可能性がある場合は、二進の浮動小数点でパースしないよう公式が注意しています。Pythonなら Decimal を使うと安全です。

from decimal import Decimal
 
def cents_to_usd(value: str) -> Decimal:
    # "41280.000000"(セント)-> Decimal("412.80")(ドル)
    return (Decimal(value) / 100).quantize(Decimal("0.01"))
 
print(cents_to_usd("41280.000000"))  # 412.80

list_amount は割引前の定価ベースの金額です。並び順は order_by で amount と list_amount から選べ、既定は amount です。割引の効き具合を見たいときは両方を並べて比べます。

actorで見分けられるユーザーの状態

各行の actor は常に user_actor 型で、user_id、email、name、deleted を持ちます。メンバーが組織から外れた場合やIdP経由でデプロビジョニングされた場合も deleted: true になり、email は残ります。アカウント自体が削除された場合は email がnullになり、name は "Deleted User" と返ります。

退職者の分を集計から外したいときは、exclude_deleted_users=true を付けます。この場合、1ページの行数が limit を下回ることがあるので、has_more と next_page で最後までたどります。

内訳を出す: group_byと絞り込み

ユーザー単位の合計だけでは、何にコストがかかっているのか分かりません。group_by[] を付けると、ユーザーの行が次元ごとに分かれます。指定できる次元は product、model、context_window、inference_geo、speed、cost_type、token_type、rbac_group_id、slack_channel_id、claude_tag_category、claude_tag_user_id の11個です。cost_report と同じ値を受け付けます。

curl -G "https://api.anthropic.com/v1/organizations/analytics/user_cost_report" \
  --data-urlencode "starting_at=2026-09-01T00:00:00Z" \
  --data-urlencode "group_by[]=product" \
  --data-urlencode "group_by[]=model" \
  --data-urlencode "limit=200" \
  --header "x-api-key: $ANALYTICS_API_KEY" \
  --header "anthropic-version: 2023-06-01"

リスト型のパラメータは、値ごとにパラメータを繰り返すブラケット記法で渡します。products[]=chat&products[]=claude_code のような形です。

次元によって limit への数え方が違います。product、model、context_window、inference_geo、speed と、bucket_width 指定時の時間バケットは、行数として limit に数えられます。1ユーザーが3モデルを使っていれば3行です。一方、cost_type と token_type は limit に数えられず、data の件数が limit を超えることがあります。cost_type はトークン・ウェブ検索・コード実行の構成要素ごとに1行、token_type はトークン種別ごとに1行を返します。

各値の意味は次のとおりです。

次元値意味
cost_type値tokens意味トークン課金の分
cost_type値web_search意味ウェブ検索の分
cost_type値code_execution意味コード実行の分
token_type値uncached_input_tokens意味キャッシュを使わない入力トークン
token_type値cache_read_input_tokens意味キャッシュから読んだ入力トークン
token_type値cache_creation.ephemeral_5m_input_tokens意味5分キャッシュの作成
token_type値cache_creation.ephemeral_1h_input_tokens意味1時間キャッシュの作成
token_type値output_tokens意味出力トークン

token_type に値が入るのは cost_type が tokens の行だけで、それ以外はnullです。cost_type か token_type で分けた行では、requests はnullになります。リクエスト数が必要なら、分けない応答から読みます。

絞り込みには products[](chat、claude_code、cowork、claude_design、claude_in_chrome、office_agent、claude-tag)、models[]、user_ids[]、rbac_group_ids[] などが使えます。たとえばClaude Codeの利用者だけを対象にする、特定のRBACグループに絞る、といった集計ができます。ユーザー管理側のAPIでメンバーを引く方法はClaude User Management APIでメンバー管理を実装するにあります。

RBACグループ別に集計すると合計が組織総額を超える

group_by[]=rbac_group_id には癖があります。ユーザーが複数のグループに属している場合、そのユーザーの全利用量が、所属する各グループの行にそれぞれ計上されます。グループ行同士は重なるので、合計が組織総額を超えることがあります。どのグループにも属さない日のユーザーは、rbac_group_id がnullの1行にまとまります。

また、Claude Tag(SlackのClaude)の claude_tag_user_id はSlackのユーザーIDで、claude.aiのユーザーIDとは別です。rbac_group_id の次元や rbac_group_ids[] フィルターとは併用できません。特定のユーザーに帰属しないClaude Tagの利用(監視や、Claudeが自発的に応答した分)は、ユーザー別の行に入らないため、ユーザー行の合計がClaude Tag全体より小さくなることがあります。

ページ送りとデータの鮮度

has_more がtrueのあいだは、next_page の値を page パラメータに渡して次のページを取得します。カーソルはそれを発行したクエリに結び付いています。products[]、group_by[]、order_by、期間、フィルターのどれかを途中で変えて古いカーソルを渡すと、400エラーになります。条件を変えたいときは、カーソルなしの最初のページからやり直します。データ更新の後にカーソルが失効することもあり、その場合はHTTP 410が返るので、同じく先頭から取り直します。

最後までたどる最小のループは、次のようになります。410を受けたら先頭からやり直す点だけ押さえてあります(やり直しは3回までで打ち切ります)。

import os
from decimal import Decimal
import requests
 
URL = "https://api.anthropic.com/v1/organizations/analytics/user_cost_report"
HEADERS = {
    "x-api-key": os.environ["ANALYTICS_API_KEY"],
    "anthropic-version": "2023-06-01",
}
PARAMS = {"starting_at": "2026-09-01T00:00:00Z", "limit": 1000}
 
def fetch_all():
    rows, page, restarts = [], None, 0
    while True:
        params = dict(PARAMS, **({"page": page} if page else {}))
        res = requests.get(URL, headers=HEADERS, params=params)
        if res.status_code == 410:  # カーソル失効: 先頭から
            restarts += 1
            if restarts > 3:
                res.raise_for_status()
            rows, page = [], None
            continue
        res.raise_for_status()
        body = res.json()
        rows += body["data"]
        if not body["has_more"]:
            return rows
        page = body["next_page"]
 
total_cents = sum(Decimal(r["amount"]) for r in fetch_all())

鮮度には3つの注意点があります。

  • データは通常4時間ごとに更新され、遅いと24時間かかることがある
  • 同じ日付の値でも、遅れて届くイベントや調整のため、最大30日は値が改訂されることがある
  • 請求と突き合わせる用途では、30日以上前の日付を対象にする

応答の data_refreshed_at は、そのレスポンスが参照したエクスポートの時刻です。ending_at を省くと、この時刻より後の未完了データが末尾に含まれます。同じ条件で繰り返し取得して結果を安定させたいときは、ending_at に前回返った data_refreshed_at 以前の時刻を指定します。該当期間をカバーするエクスポートがまだなければ、data_refreshed_at はnullで data も空です。

レート制限はAPIキー単位ではなく組織単位で、既定ではAnalytics API全体で毎分60リクエストです。ユーザー別に何度も内訳を取り直す実装では、この上限に当たりやすくなります。

時間で分割する: bucket_width

user_cost_report にも bucket_width(1d、1h、1m)があり、指定すると各行に starting_at と ending_at が入り、1ユーザーが時間バケットごとの複数行に分かれます。このとき ending_at は必須です。1m を選ぶと、範囲は最大24時間に制限されます。省略した場合は、期間全体を1行に集約します。

推移をユーザー別に見たいだけなら、この指定で足ります。ユーザーを分けずに日次の推移と総額が欲しいなら、cost_report のほうが素直です。

使い分けの早見表

やりたいこと使うエンドポイント
今月いちばん使っているユーザーの上位を出す使うエンドポイントuser_cost_report
ユーザーごとに、どの製品・モデルで使ったかを見る使うエンドポイントuser_cost_report + group_by[]
組織全体の日次コストの推移を見る使うエンドポイントcost_report
APIキー直叩きや自動化を含めた組織総額を出す使うエンドポイントcost_report
支出上限の増額を検討するメンバーを探す使うエンドポイントuser_cost_report(運用はClaude支出上限APIの記事)

ユーザー別の合計と組織の総額が一致しないのは、仕様どおりの動作です。差分はAPIキー直叩きや自動化などの、席ユーザーに帰属しないコストに当たります。突き合わせるなら、cost_report の総額から user_cost_report の合計を引いた差が、その帰属先のコストの目安になります。

つまずきやすい点

Claude Console側の使用量・コストを画面で確認したいなら、APIを組む前にClaude Consoleの使用量・コストレポートの見方で足りる場合があります。Enterprise組織のコスト取得でこのAPIを選ぶのは、メンバー単位・グループ単位の集計が必要なときです。

  • シート課金型プランでは利用クレジットの分しか反映されない。請求額との差はプランの種類から疑う
  • Claude CodeをAmazon Bedrock経由で使っている場合、そのClaude Codeの利用はAnalytics APIに返らない
  • amount はセント単位の文字列で、浮動小数点でパースしない
  • カーソルを持ったまま group_by[] や期間を変えると400になる
  • deleted: true の行には退職者が含まれるので、配賦の対象から外すかどうかを先に決める

まとめ

メンバー別の請求配賦や高額利用者の特定には user_cost_report、日次の推移やAPIキー直叩きを含む組織総額には cost_report を使います。前者の合計が後者の総額に届かないのは仕様で、差分は席ユーザーに帰属しないコストです。実装では、amount を Decimal で扱うことと、カーソルの失効(410)で先頭からやり直すことの2点を先に入れておくと、集計が崩れにくくなります。

この記事を共有:XはてブLinkedIn