OpenAIの請求書には、先月4,237ドル使ったと書かれています。しかし、そのうち3,100ドルは暴走した要約エンドポイントから、700ドルは月に50ドル支払っている顧客から、437ドルは誰も使わない機能から発生したことは書かれていません。ダッシュボードでは、価格設定、キャパシティ、ロードマップの判断に必要な情報が隠れています。
このガイドでは、OpenAI APIのコストを機能、ルート、顧客、環境ごとに割り当てる実装方法を説明します。すべてのリクエストにメタデータを付与し、トークン数とコストを構造化ログとして出力し、ウェアハウスで集計し、予算上限とアラートを設定します。
💡 Apidogは、コスト追跡ラッパーを本番環境に出す前に、リクエストレベルの可視性とシナリオテストを提供します。タグ付きリクエストの再生、ログ形式のアサート、すべての呼び出しがウェアハウスの期待するメタデータを持つことの検証に使えます。
TL;DR
OpenAI API呼び出しごとに、以下を必ず記録します。
featureroutecustomer_idenvironmentmodel- トークン数
- 計算済みの
cost_usd
そのうえで、ウェアハウスでタグごとに集計し、OpenAI側ではキーごとの予算上限を設定します。さらに、時間ごとの支出異常を検知し、リリース前にApidogのシナリオテストでラッパーを検証します。
はじめに
火曜日に新しいAI機能をリリースしました。金曜日の朝、CFOから「OpenAIの利用料が40%も跳ね上がったのはなぜだ」とDMが来ます。OpenAIダッシュボードを見ると、合計支出が増えていることは分かります。しかし、どの機能、どの顧客、どのルートが原因かは分かりません。
これは、本番環境でLLMワークロードを運用するチームが必ず直面する問題です。OpenAIの請求インターフェースは経理向けであり、エンジニアリングやプロダクトの帰属分析向けではありません。
この記事では、次を実装します。
- OpenAIクライアントのラッパー
- コスト帰属用のイベントスキーマ
- トークン数からのコスト計算
- 構造化ログ出力
- ウェアハウス集計SQL
- ApidogによるE2E検証
- 予算上限と異常検知
価格計算の前提については、GPT-5.5の価格内訳を参照してください。開発者ツール側の請求帰属については、APIチーム向けのGitHub Copilot利用料請求も参考になります。OpenAI APIの基本は公式のOpenAI APIリファレンスを確認してください。
OpenAIの課金ダッシュボードでは不十分な理由
OpenAIの課金ページでは、主に次が確認できます。
- 日別の支出
- モデル別の使用量
- 組織レベルの使用制限
単一アプリ、単一機能、単一顧客なら十分です。しかし、実際のプロダクトでは複数の機能、顧客、環境、開発者が同じOpenAI組織を使います。
不足する情報は次のとおりです。
コンテキストのない総支出
ダッシュボードに「昨日GPT-5.5に312ドル使った」と表示されても、それがサポートチャットの大量呼び出しなのか、バックグラウンド要約ジョブの暴走なのかは分かりません。
機能ごとの内訳がない
OpenAIはAPIキーやモデル単位では集計できますが、あなたのプロダクト上のfeature、route、customer_id、environmentでは集計しません。
レポートに遅延がある
使用状況データは数十分から数時間遅れて表示されます。暴走ループの検知には遅すぎます。
アラートが粗い
OpenAI側の予算通知だけでは、「チャット機能が1時間に50ドルを超えたら通知する」といった制御はできません。
顧客帰属がない
B2B SaaSでAI機能を提供している場合、顧客ごとのAI原価を把握しないと粗利益を計算できません。
プロジェクトキーだけでは粒度が足りない
OpenAIのプロジェクトキーは有用ですが、機能、顧客、ルート単位の帰属には不十分です。OpenAI usage APIも、基本的には集計済みデータを返すため、リクエスト単位のメタデータはアプリケーション側で持つ必要があります。
この問題は多くのチームに共通しています。Dev.toでも「OpenAIはいくら使ったかは教えてくれる。どこで使ったかは教えてくれない」という文脈で議論されています。
コスト帰属のデータモデル
まず、OpenAIリクエスト1回につき1つのイベントを記録します。このイベントが分析単位です。
最小スキーマは次のとおりです。
| カラム | 型 | 例 | 目的 |
|---|---|---|---|
request_id |
uuid | 7a91... |
冪等性、重複排除、リトライ |
timestamp |
timestamptz | 2026-05-06T14:23:01Z |
時系列分析、異常検知 |
feature |
text | support-chat |
呼び出し元のプロダクト機能 |
route |
text | /api/v1/chat/answer |
HTTPルートまたはジョブID |
customer_id |
text | cust_4291 |
顧客ごとの支出 |
environment |
text | prod |
本番、ステージング、開発の分離 |
model |
text | gpt-5.5 |
モデル別価格計算 |
prompt_tokens |
int | 15234 |
入力トークン数 |
completion_tokens |
int | 812 |
出力トークン数 |
reasoning_tokens |
int | 4500 |
推論トークン |
cached_tokens |
int | 12000 |
キャッシュ済み入力トークン |
latency_ms |
int | 2341 |
レイテンシ分析 |
cost_usd |
numeric | 0.045672 |
書き込み時に計算したコスト |
prompt_cache_key |
text | system-v3 |
キャッシュヒット率の追跡 |
error_code |
text |
null / 429
|
エラーとリトライ分析 |
重要なのは、cost_usdをクエリ時ではなく書き込み時に計算することです。価格は変更されるため、履歴イベントには「その時点のレート」で計算した値を固定して保存します。
コスト計算を実装する
GPT-5.5系の価格表をコードに固定します。
PRICING = { # USD per 1M tokens, as of May 2026
"gpt-5.5": {"input": 5.00, "cached": 2.50, "output": 30.00},
"gpt-5.5-pro": {"input": 30.00, "cached": 15.00, "output": 180.00},
"gpt-5.4": {"input": 2.50, "cached": 1.25, "output": 15.00},
"gpt-5.4-mini": {"input": 0.25, "cached": 0.125, "output": 2.00},
}
def compute_cost_usd(
model,
prompt_tokens,
cached_tokens,
completion_tokens,
reasoning_tokens
):
rates = PRICING[model]
uncached = max(0, prompt_tokens - cached_tokens)
input_cost = (uncached * rates["input"]) / 1_000_000
cache_cost = (cached_tokens * rates["cached"]) / 1_000_000
output_cost = (
(completion_tokens + reasoning_tokens) * rates["output"]
) / 1_000_000
return round(input_cost + cache_cost + output_cost, 6)
推論トークンは出力として扱います。OpenAI APIではusage.completion_tokens_details.reasoning_tokensとして返されますが、課金上は出力レートです。ここを間違えると、Thinking系の呼び出しコストを過小評価します。
詳細な価格計算はGPT-5.5の価格内訳を参照してください。
OpenAIクライアントをラップする
すべてのOpenAI呼び出しを1つの関数に集約します。
import time
import uuid
import json
import logging
from openai import OpenAI
client = OpenAI()
logger = logging.getLogger("llm.cost")
def call_with_attribution(
*,
feature,
route,
customer_id,
environment,
model,
messages,
**openai_kwargs
):
if not feature or not route or not customer_id or not environment:
raise ValueError("feature, route, customer_id, environment are required")
request_id = str(uuid.uuid4())
started = time.time()
error_code = None
response = None
try:
response = client.chat.completions.create(
model=model,
messages=messages,
**openai_kwargs
)
return response
except Exception as e:
error_code = getattr(e, "code", "unknown_error")
raise
finally:
latency_ms = int((time.time() - started) * 1000)
u = response.usage if response else None
prompt_tokens = getattr(u, "prompt_tokens", 0)
completion_tokens = getattr(u, "completion_tokens", 0)
cached_tokens = (
getattr(
getattr(u, "prompt_tokens_details", None),
"cached_tokens",
0
) or 0
)
reasoning_tokens = (
getattr(
getattr(u, "completion_tokens_details", None),
"reasoning_tokens",
0
) or 0
)
cost_usd = compute_cost_usd(
model,
prompt_tokens,
cached_tokens,
completion_tokens,
reasoning_tokens
)
logger.info(json.dumps({
"event": "openai.request",
"request_id": request_id,
"feature": feature,
"route": route,
"customer_id": customer_id,
"environment": environment,
"model": model,
"prompt_tokens": prompt_tokens,
"completion_tokens": completion_tokens,
"reasoning_tokens": reasoning_tokens,
"cached_tokens": cached_tokens,
"latency_ms": latency_ms,
"cost_usd": cost_usd,
"error_code": error_code,
}))
このラッパーを、コスト帰属の唯一の入口にします。
やることは明確です。
- コードベースで
OpenAI(を検索する -
client.chat.completions.createの直接呼び出しを禁止する - すべて
call_with_attribution(...)に置き換える -
feature、route、customer_id、environmentを必須にする - 不明な値を
unknownで埋めず、呼び出し時に失敗させる
Node.jsでも構造は同じです。OpenAI SDKを関数で包み、response.usageを読み取り、JSONイベントを書き込みます。Kafka、NATS、Pub/Subなどのイベントバスがある場合は、stdoutではなくそこに発行しても構いません。
コスト追跡を構築し、Apidogでテストする
実装手順は6ステップです。
1. 直接のOpenAI呼び出しをラッパーに置き換える
コードベースで次を検索します。
grep -R "OpenAI(" .
grep -R "chat.completions.create" .
見つかった呼び出しをすべてcall_with_attribution(...)に置き換えます。
呼び出し例:
response = call_with_attribution(
feature="support-chat",
route="/api/v1/chat/answer",
customer_id=current_user.customer_id,
environment="prod",
model="gpt-5.5",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_question},
],
)
2. 構造化ログを出力する
各イベントは1行のJSONで出力します。
{
"event": "openai.request",
"request_id": "7a91...",
"feature": "support-chat",
"route": "/api/v1/chat/answer",
"customer_id": "cust_4291",
"environment": "prod",
"model": "gpt-5.5",
"prompt_tokens": 15234,
"completion_tokens": 812,
"reasoning_tokens": 4500,
"cached_tokens": 12000,
"latency_ms": 2341,
"cost_usd": 0.045672,
"error_code": null
}
このログを既存のパイプラインでBigQuery、ClickHouse、Snowflake、Postgresなどに送ります。
3. ウェアハウスで機能ごとに集計する
SELECT
feature,
DATE_TRUNC(timestamp, DAY) AS day,
COUNT(*) AS requests,
SUM(cost_usd) AS spend_usd,
SUM(prompt_tokens + completion_tokens) AS tokens,
AVG(latency_ms) AS avg_latency_ms,
SUM(cached_tokens) / NULLIF(SUM(prompt_tokens), 0) AS cache_hit_rate
FROM openai_events
WHERE environment = 'prod'
AND timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)
GROUP BY feature, day
ORDER BY day DESC, spend_usd DESC;
次のビューを作ると運用しやすくなります。
- 機能ごとの日次支出
- 顧客ごとの日次支出
- ルート別の上位支出
- モデル別の支出
- キャッシュヒット率
- 平均プロンプトトークン数
- 平均出力トークン数
4. ルートごとの支出をグラフ化する
Grafana、Metabase、Looker、Supersetなどで可視化します。
最低限、次の3つは作ってください。
- 機能別支出の時系列
- 顧客別支出の時系列
- 昨日の支出が多い上位20ルート
これが、OpenAIダッシュボードの代わりに毎日見る運用ダッシュボードになります。
5. リリース前にApidogでラッパーをテストする
ラッパーのバグは静かにダッシュボードを壊します。特に危険なのは、ログが出ているように見えて、customer_idやfeatureが欠落している状態です。
Apidogで次を検証します。
- 既知の
customer_idとfeatureを持つリクエストをAIエンドポイントに送る - レスポンスを検証する
- stdout、OTLP、ログエンドポイントなどのサイドチャネルを確認する
- ログペイロードに
feature、route、customer_idが含まれることをアサートする -
cost_usd > 0とprompt_tokens > 0をアサートする - ステージングと本番で同じシナリオを実行する
- リトライ時にコストが二重計上されないことを確認する
APIテスト全般については、QAエンジニア向けのAPIテストツールを参照してください。契約優先でAPIを設計する場合は、契約優先API開発も参考になります。
6. キーごとの予算上限とアラートを設定する
OpenAI側では、環境や主要機能ごとにプロジェクトキーを分けます。
例:
prod-support-chatprod-summarizationprod-agentstaging-all
それぞれにOpenAIダッシュボードで上限を設定します。
ただし、ネイティブの上限だけでは不十分です。ウェアハウス側でも異常検知します。
例:10分ごとに実行する監視SQL
WITH hourly AS (
SELECT
feature,
TIMESTAMP_TRUNC(timestamp, HOUR) AS hour,
SUM(cost_usd) AS spend_usd
FROM openai_events
WHERE environment = 'prod'
AND timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 8 DAY)
GROUP BY feature, hour
),
baseline AS (
SELECT
feature,
AVG(spend_usd) AS avg_hourly_spend
FROM hourly
WHERE hour < TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 HOUR)
GROUP BY feature
),
current_hour AS (
SELECT
feature,
SUM(cost_usd) AS current_spend
FROM openai_events
WHERE environment = 'prod'
AND timestamp >= TIMESTAMP_TRUNC(CURRENT_TIMESTAMP(), HOUR)
GROUP BY feature
)
SELECT
c.feature,
c.current_spend,
b.avg_hourly_spend
FROM current_hour c
JOIN baseline b USING (feature)
WHERE c.current_spend > b.avg_hourly_spend * 3;
結果が返ったらSlack、PagerDuty、Opsgenieなどに通知します。
ネイティブ上限は最後の防衛線、ウェアハウスアラートは早期検知です。
高度なテクニック
プロンプトキャッシングを前提にプロンプトを設計する
GPT-5.5では、キャッシュされたトークンは入力レートの50%で課金されます。システムプロンプトを安定したプレフィックスとして配置し、リクエストごとの変数を末尾に置きます。
追跡すべき指標:
SELECT
feature,
SUM(cached_tokens) / NULLIF(SUM(prompt_tokens), 0) AS cache_hit_rate
FROM openai_events
WHERE environment = 'prod'
GROUP BY feature
ORDER BY cache_hit_rate ASC;
公式のOpenAIプロンプトキャッシングドキュメントも確認してください。
オフライン処理はBatch APIに寄せる
同期応答が不要な処理はBatch APIに回します。
対象例:
- 夜間要約
- 評価実行
- 埋め込みのバックフィル
- ドキュメント再処理
Batch呼び出しにも同じコスト帰属を適用し、イベントにbatch_job_idを追加します。
推論努力をチューニングする
GPT-5.5 Thinkingでは、reasoning.effortによって推論トークンが変わります。mediumで動かしている機能が、lowでも品質基準を満たすか確認してください。
やること:
-
reasoning.effort別にA/Bテストする - 品質指標を比較する
-
cost_usdを比較する - 品質が維持される最安設定を採用する
詳細はGPT-5.5 APIの使用方法を参照してください。
コンテキストウィンドウを管理する
プロンプトが長いほどコストは増えます。RAGでは、知識ベース全体を入れるのではなく、取得件数とトークン予算を明示的に制限します。
監視SQL:
SELECT
feature,
DATE_TRUNC(timestamp, WEEK) AS week,
AVG(prompt_tokens) AS avg_prompt_tokens
FROM openai_events
WHERE environment = 'prod'
GROUP BY feature, week
ORDER BY week DESC, avg_prompt_tokens DESC;
機能変更がないのにavg_prompt_tokensが増えている場合、プロンプトが肥大化しています。
GPT-5.5の272Kトークンクリフを監視する
GPT-5.5では、272Kトークンを超えるリクエストに対して、入力に2倍、出力に1.5倍の乗数が適用されます。
ラッパーにガードを追加します。
if prompt_tokens > 250_000:
logger.warning(json.dumps({
"event": "openai.prompt_token_warning",
"request_id": request_id,
"feature": feature,
"route": route,
"customer_id": customer_id,
"prompt_tokens": prompt_tokens,
}))
価格の詳細はGPT-5.5の価格に関する投稿を参照してください。
顧客ごとの支出上限を設定する
B2B SaaSでは、顧客ごとのAI原価を制御する必要があります。
実装方針:
- ウェアハウスまたは高速ストアで
customer_idごとの月次支出を集計 - 各OpenAI呼び出し前に上限をチェック
- 上限超過時は429を返す
- レスポンスに課金CTAを含める
例:
def assert_customer_budget(customer_id):
spend = get_monthly_ai_spend(customer_id)
limit = get_customer_ai_limit(customer_id)
if spend >= limit:
raise AIQuotaExceeded(
"月間AIクォータを超過しました。プランのアップグレードを検討してください。"
)
避けるべきミス
- 推論トークンを入力として課金する
- リアルタイム監視にOpenAIダッシュボードだけを使う
- 呼び出しサイトではなくSDKレベルで雑にタグ付けする
- cron、キューワーカー、Webhookのタグ付けを忘れる
- リクエストログをサンプリングする
-
customer_idをnullのままにする - リトライ時に
request_idを再利用せず二重計上する
バックグラウンドジョブには、次のような合成routeを付けます。
cron:nightly-summarizequeue:image-captionwebhook:customer-import
customer_idが存在しない内部処理では、nullではなくinternalやsystemを使います。
代替手段とツール
自前実装以外の選択肢もあります。
| アプローチ | 得意な点 | コスト | 向いているケース |
|---|---|---|---|
| OpenAI usage API | ネイティブ、セットアップ不要 | 無料 | 1プロジェクト、1機能、顧客帰属不要 |
| Helicone | ドロップインプロキシ、ダッシュボード、キャッシュ | 無料枠あり、月額20ドル〜 | 早く可視化したい、プロキシを許容できる |
| Langfuse | OSS、セルフホスト、トレース + コスト | セルフホスト無料、クラウド月額29ドル〜 | トレースとコストを一体で管理したい |
| LangSmith | LangChain統合、評価 + コスト | 月額39ドル/ユーザー〜 | LangChainをすでに使っている |
| カスタムウェアハウス | 完全制御、既存スタックに統合 | エンジニアリング時間 | 大規模、独自ディメンション、データ所在地要件あり |
プロキシ型のHeliconeは導入が速い一方、クリティカルパスにホップが増えます。Langfuseは制御しやすいですが、セルフホストする場合は運用が必要です。カスタムウェアハウスは実装コストがありますが、大規模チームでは最終的にこの形に寄ることが多いです。
LLMコスト可観測性の実装例として、HeliconeチームのLLMコスト追跡に関するガイドとLangfuseのコスト追跡に関するドキュメントも参考になります。
プラットフォーム規模でこのパターンを運用する場合は、マイクロサービスアーキテクチャのためのAPIプラットフォームも参照してください。
実世界のユースケース
顧客ごとのLLM支出を持つB2B SaaS
あるセールスインテリジェンス製品では、顧客がブリーフィングを要求するたびにGPT-5.5呼び出しが発生します。帰属なしでは、月8万ドルのOpenAI支出しか分かりません。
顧客ごとの帰属を入れると、顧客の12%が支出の71%を占めていることが分かりました。そこで段階的価格、ソフトクォータ、超過料金を導入し、AI機能の粗利益を改善できます。
社内開発ツールの追跡
エンジニア向けの社内GPTアシスタントでも同じです。customer_idに開発者メールを入れると、誰がどれだけ使っているかが分かります。
異常な支出を見つけることで、放置された自動エージェントループを停止できます。一方、正当な高利用者にはより高いクォータを割り当てる判断もできます。
AI機能の支出予測
新しい要約機能を出す前に、過去の機能別データから次を見積もります。
- 呼び出しあたりの平均入力トークン
- 呼び出しあたりの平均出力トークン
- アクティブユーザーあたりの想定呼び出し回数
- 想定アクティブユーザー数
これにより、機能単位の原価を事前に計算できます。価格設定やリリース可否の判断が推測ではなくなります。
結論
測定できないものは管理できません。OpenAIの課金ダッシュボードは財務上の合計金額を示しますが、プロダクト運用には機能、顧客、ルートごとの帰属が必要です。
実装すべきことはシンプルです。
- すべてのリクエストに
feature、route、customer_id、environmentを付ける - OpenAIクライアントをラッパー経由に統一する
- トークン数と
cost_usdを構造化ログで出力する - ウェアハウスで集計する
- OpenAIプロジェクトキーごとに上限を設定する
- ウェアハウス側で異常検知する
- リリース前にApidogでラッパーを検証する
Apidogをダウンロードして、コスト帰属ラッパーのE2E検証に使ってください。タグ付きリクエストでAIエンドポイントを実行し、ログペイロードの形状をアサートし、複数環境でシナリオを再生できます。
関連する読み物:
よくある質問
推論トークンは入力として課金されますか?出力として課金されますか?
出力レートで課金されます。OpenAI APIではusage.completion_tokens_details.reasoning_tokensとして返されるため、completion_tokensに加算してコスト計算してください。詳細はGPT-5.5の価格内訳を参照してください。
response.usageはOpenAIダッシュボードと一致しますか?
トークン数はダッシュボードと一致します。ただし、古い料金表でコストを計算していると、価格変更によってずれます。モデルごとのレートはコードまたは設定で固定し、価格変更日に更新してください。
OpenAIのプロジェクトキーだけで帰属できますか?
一部は可能です。プロジェクト単位の帰属や予算上限には有効です。ただし、機能、顧客、ルート単位の帰属にはアプリケーションレベルのメタデータが必要です。
リトライでコストが二重計上されませんか?
モデル実行前に失敗したリクエストは通常usageを返さないため、コストは記録されません。成功後にアプリケーション側でリトライすると、request_idを再利用しない限り二重計上されます。冪等なリトライでは同じrequest_idを使い、書き込み時に重複排除してください。
OpenAI usage APIはリアルタイム監視に使えますか?
リアルタイム監視には不向きです。数十分の遅延があります。アラートやキルスイッチには自分のログとウェアハウスを使い、月次調整にはusage APIを使うのが現実的です。
ログ量を減らすためにサンプリングしてもよいですか?
いいえ。リクエストごとに1行のJSONで済むため、データ量は小さいです。サンプリングすると顧客別、ルート別の正確な帰属が壊れます。すべて記録してください。
他のLLMプロバイダーにも使えますか?
使えます。providerカラムを追加し、openai、anthropic、google、deepseekなどを入れます。プロバイダーごとに料金表とラッパーは変わりますが、ウェアハウスのスキーマとダッシュボードは共通化できます。比較としてDeepSeek V4 APIの価格設定も参照してください。
埋め込みや画像生成にも使えますか?
使えます。ただし、コスト計算はエンドポイントごとに分岐します。埋め込みは入力トークン単位、画像生成は画像枚数や解像度単位で計算します。スキーマにendpointを追加し、chat、embeddings、imageなどで分けてください。
Top comments (0)