AI APIの請求が概算の4〜6倍に? 想定外コストの3つの原因と検出法【Claude/OpenAI/Gemini・2026年版】

執筆・監修: Links-Create AI研修チーム
Claude Code・MCP・AI エージェントを実プロダクト開発で日常的に運用するチームが、 実務で詰まった点に基づいて執筆しています。 公開: 2026-06-17

この記事でわかること

  • 概算と実請求のずれは『使いすぎ』でなく計測の盲点。下の3機序を上から潰す
  • 機序1: 思考(reasoning)トークンが『見える出力』に乗らず過小計上される。不要なら思考予算をゼロに
  • 機序2: 課金tierはenv変数名でなく『キーが属する課金プロジェクト』で決まる。sha256フィンガープリントで照合
  • 機序3: 請求集計の通貨取り違え(JPY/USD)と失敗の握り潰しで桁がずれる。実Billing Exportと突合する
  • 無料枠の共有枯渇で429が連鎖。高頻度の呼び出しには有料フォールバックを設計する

検証日: 2026-06。各社の課金仕様は改定が速い。金額・設定フラグは必ず公式 pricing / docs で最新を確認すること。本記事は実運用で踏んだ実測例に基づく診断手順であり、特定構成を推奨するものではない。

自前ログでは「今月のAPI利用はだいたい数百円」のはずだった。ところが実際の請求は、その4〜6倍。ある月の実測例では、自前集計の概算が約¥287、実請求が約¥886。

原因は「使いすぎ」ではなかった。自前の概算が構造的に過小だった。トークンを多く使ったのではなく、請求を正しく数えられていなかった。同じ罠は Gemini / Claude / OpenAI のどれでも起きる。踏んだ3つの機序と、それぞれの検出法を残す。

機序1: 思考トークンは「見える出力」に含まれない

最初に効いたのがこれだった。

gemini-2.5-flash のような推論モデルは、回答を返す前に内部で「思考」する。この思考トークンは課金される。ところがレスポンスの candidates_token_count(=最終的に見える出力のトークン数)には乗らない。別フィールド(thoughts_token_count)に入る。思考の費用感は Claude Code の思考とコスト でも触れている。

自前のコスト集計を素朴に書くと、こうなる。

# 過小計上になる集計
out_tokens = resp.usage.candidates_token_count   # 思考分が抜ける
cost = out_tokens * OUTPUT_PRICE

見えている出力だけ数えて満足してしまう。思考が重いプロンプトほど、概算と実請求の差は開く。

検出: レスポンスの usage を全フィールド出す。candidates_token_countthoughts_token_count(または各社の推論トークン相当)を別々にログする。両者の比を見れば「見えないトークン」の比率が分かる。

抑制: 思考が不要なタスクは思考予算をゼロにする。あるバッチではこの一点で実コストが約4割下がった。

# Gemini: 思考を切る(タスクが思考を必要としない場合のみ)
config = {"thinking_config": {"thinking_budget": 0}}

各社で扱いが違う(要公式確認)

観点GeminiClaudeOpenAI
推論トークンの呼称thoughts_token_countextended thinking のトークンreasoning_tokens
「見える出力」に含まれるか含まれない(別フィールド)各社 docs を確認completion 内に計上
予算で抑制できるかthinking_budgetbudget_tokensreasoning effort

この表は2026-06時点の整理。各社で課金扱いが異なり仕様変更も速いため、額面で信用せず、自分のアカウントの usage レスポンスと公式 pricing で実測・再確認すること。

機序2: 「無料枠のはず」が課金される — tier は env 変数名ではなくキーの所属で決まる

次がいちばん見つけにくかった。

無料枠で回しているつもりの呼び出しが、全部課金されていた。コードは環境変数のスロット名で tier を判定していた。

# 誤り: env 変数名で「無料」と決めつける
if key_env_name == "GEMINI_API_KEY":   # 「無料用のつもり」
    tier = "free"

ところが課金は env 変数名では決まらない。そのキーが属する課金プロジェクトで決まる。「無料用」のつもりの env 変数に、実は課金プロジェクトのキーが入っていた。さらに「有料用」の別変数にも同じ課金キーが入り、本物の無料キーはどこにも設定されていなかった。結果、「無料化」したはずの全呼び出しが課金プロジェクトに流れ、ログはすべて tier=free と取り違えた。

裏取りは API コンソールのプロジェクト別リクエスト数で取れる。「無料プロジェクト 19 リクエスト / 課金プロジェクト 822 リクエスト」のように、ログの自己申告と実トラフィックがずれていれば確定。

検出(再現可能): env 変数名を信用せず、キー実体の sha256 フィンガープリントで所属プロジェクトを照合する。秘密値そのものはログに出さず、先頭16桁のハッシュだけ突き合わせる。

import hashlib

def key_fingerprint(api_key: str) -> str:
    return hashlib.sha256(api_key.encode()).hexdigest()[:16]

# フィンガープリント → 実プロジェクト の検証マップ(値は合成例)
KEY_PROJECT = {
    "3f9a1c2e8b7d4056": {"project": "prj-billing-xxxx", "tier": "paid"},
    "a1b2c3d4e5f60718": {"project": "prj-free-xxxx",    "tier": "free"},
}

def classify(api_key: str) -> dict:
    fp = key_fingerprint(api_key)
    return KEY_PROJECT.get(fp, {"project": "unknown", "tier": "unknown"})  # 未登録は warn

これで「env 名は free だが実体は課金キー」を一発で炙り出せる。

おまけの罠: 実行経路でキーが入れ替わる

対話シェルの初期化ファイル(.bashrc 等)が、非対話の cron とは別のキーで環境変数を上書きしていることがある。同じコードでも、手で叩いたときと cron で動いたときでキーが変わる。.bashrc で API キーを export しているなら、そこが「無料のはず」を壊している第一容疑者になる。

機序3: 通貨・単位の取り違えと「無言の劣化」

最後は集計側のバグ。

実コストを請求エクスポート(BigQuery 等)から取る突合スクリプトが、通貨を取り違えていた。エクスポートの通貨は JPY なのに、コードは値を USD としてラベリングしていた。150倍ずれて、概算との差分が無意味になっていた。

-- 通貨を明示せず取り出すと JPY/USD が混ざる
SELECT SUM(cost) AS cost_usd  -- 実は JPY

さらに悪いことに、このスクリプトは突合クエリの失敗を握り潰していた

# returncode も stderr も見ない → 失敗が空配列に化けて無言で劣化
out = subprocess.run(["bq", "query", ...], capture_output=True, text=True)
rows = json.loads(out.stdout or "[]")   # 失敗時 [] → 「実コスト n/a」のまま古いデータで放置

実コストの真値が数週間止まっていたのに、ダッシュボードは平然と古い概算を出し続けていた。

対策:

  • 自前概算と実請求エクスポートを必ず突合する。片方だけ見ない。
  • 通貨をクエリで明示し(GROUP BY ... , currency)、換算レートを定数で持って JPY↔USD を明示変換する。
  • サブプロセスの returncodestderr を必ずチェックし、失敗を空データに化けさせない。換算レートが inf/0 でないことも確認する。

おまけ: 無料枠の「共有枯渇」で429が連鎖する

無料枠はプロジェクト単位の RPD(1日あたりリクエスト数)で頭打ちになる。複数のサービスが同じ無料プロジェクトの同じモデルに殺到すると、枠を食い合って 429 quota exceeded が連鎖する。別の月の監査では、全リクエストの約 23.8%(14,354 中 3,415) が 429 で落ちていた。429 の切り分けは Claude Code のエラー対処 も参考になる。

有料フォールバックを実装していないサービスは、ここで機能停止する。高頻度のバッチには有料フォールバックの経路を用意し、低頻度のものだけ無料枠に残すのが現実的だった。そもそもの課金を避けたいなら、Ollama でローカルLLM に逃がす選択肢もある。

持ち帰りチェックリスト

請求が概算と合わないときに上から潰す。

  1. 思考トークンを集計に含めているかcandidates_token_count だけ数えていないか。推論トークンを別計上したか。不要なら思考予算をゼロに。
  2. キーの所属プロジェクトを実体で確認したか — env 変数名でなく sha256 フィンガープリントで照合。.bashrc の export を疑う。
  3. 自前概算と実請求エクスポートを突合したか — 片方だけ信用しない。
  4. 通貨・単位は合っているか — JPY/USD の取り違え(150倍)に注意。
  5. 集計クエリの失敗を握り潰していないかreturncode/stderr を見ているか。古いデータの無言劣化を検出。
  6. 無料枠の共有枯渇に備えているか — 429連鎖と有料フォールバックの設計。

免責: API キーの無効化や Spending Limit のゼロ化は、誤って実行すると本番サービスを止める。停止系の操作は影響範囲を確認し、可逆な手順で段階的に行うこと。課金・料金・設定フラグは陳腐化が速いため、本記事の値は2026-06時点の実測例として扱い、各社公式 pricing / docs で最新を必ず確認すること。

関連するコース・ガイド