AI APIの請求が概算の4〜6倍に? 想定外コストの3つの原因と検出法【Claude/OpenAI/Gemini・2026年版】
この記事でわかること
- 概算と実請求のずれは『使いすぎ』でなく計測の盲点。下の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_count と thoughts_token_count(または各社の推論トークン相当)を別々にログする。両者の比を見れば「見えないトークン」の比率が分かる。
抑制: 思考が不要なタスクは思考予算をゼロにする。あるバッチではこの一点で実コストが約4割下がった。
# Gemini: 思考を切る(タスクが思考を必要としない場合のみ)
config = {"thinking_config": {"thinking_budget": 0}}
各社で扱いが違う(要公式確認)
| 観点 | Gemini | Claude | OpenAI |
|---|---|---|---|
| 推論トークンの呼称 | thoughts_token_count | extended thinking のトークン | reasoning_tokens |
| 「見える出力」に含まれるか | 含まれない(別フィールド) | 各社 docs を確認 | completion 内に計上 |
| 予算で抑制できるか | thinking_budget | budget_tokens | reasoning 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 を明示変換する。 - サブプロセスの
returncodeとstderrを必ずチェックし、失敗を空データに化けさせない。換算レートがinf/0でないことも確認する。
おまけ: 無料枠の「共有枯渇」で429が連鎖する
無料枠はプロジェクト単位の RPD(1日あたりリクエスト数)で頭打ちになる。複数のサービスが同じ無料プロジェクトの同じモデルに殺到すると、枠を食い合って 429 quota exceeded が連鎖する。別の月の監査では、全リクエストの約 23.8%(14,354 中 3,415) が 429 で落ちていた。429 の切り分けは Claude Code のエラー対処 も参考になる。
有料フォールバックを実装していないサービスは、ここで機能停止する。高頻度のバッチには有料フォールバックの経路を用意し、低頻度のものだけ無料枠に残すのが現実的だった。そもそもの課金を避けたいなら、Ollama でローカルLLM に逃がす選択肢もある。
持ち帰りチェックリスト
請求が概算と合わないときに上から潰す。
- 思考トークンを集計に含めているか —
candidates_token_countだけ数えていないか。推論トークンを別計上したか。不要なら思考予算をゼロに。 - キーの所属プロジェクトを実体で確認したか — env 変数名でなく sha256 フィンガープリントで照合。
.bashrcの export を疑う。 - 自前概算と実請求エクスポートを突合したか — 片方だけ信用しない。
- 通貨・単位は合っているか — JPY/USD の取り違え(150倍)に注意。
- 集計クエリの失敗を握り潰していないか —
returncode/stderrを見ているか。古いデータの無言劣化を検出。 - 無料枠の共有枯渇に備えているか — 429連鎖と有料フォールバックの設計。
免責: API キーの無効化や Spending Limit のゼロ化は、誤って実行すると本番サービスを止める。停止系の操作は影響範囲を確認し、可逆な手順で段階的に行うこと。課金・料金・設定フラグは陳腐化が速いため、本記事の値は2026-06時点の実測例として扱い、各社公式 pricing / docs で最新を必ず確認すること。