OpenAI API利用料金を機能別に追跡する方法:コスト配分プレイブック
OpenAIの請求書には、先月4,237ドル使ったと書かれています。しかし、そのうち3,100ドルは暴走した要約エンドポイントから、700ドルは月に50ドル支払っている顧客から、437ドルは誰も使わない機能から発生したことは書かれていません。ダッシュボードでは、価格設定、キャパシティ、ロードマップの判断に必要な情報が隠れています。 今すぐApidogを試す このガイドでは、OpenAI APIのコストを機能、ルート、顧客、環境ごとに割り当てる実装方法を説明します。すべてのリクエストにメタデータを付与し、トークン数とコストを構造化ログとして出力し、ウェアハウスで集計し、予算上限とアラートを設定します。 💡 Apidogは、コスト追跡ラッパーを本番環境に出す前に、リクエストレベルの可視性とシナリオテストを提供します。タグ付きリクエストの再生、ログ形式のアサート、すべての呼び出しがウェアハウスの期待するメタデータを持つことの検証に使えます。 OpenAI API呼び出しごとに、以下を必ず記録します。 feature route customer_id environment model トークン数 計算済みの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組織を使います。 不足する情報は次のとおりです。 ダッシュボードに「昨日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呼び出しを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ではなくそこに発行しても構いません。 実装手順は6ステップです。 コードベースで次を検索します。 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}, ], ) 各イベントは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などに送ります。 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; 次のビューを作ると運用しやすくなります。 機能ごとの日次支出 顧客ごとの日次支出 ルート別の上位支出 モデル別の支出 キャッシュヒット率 平均プロンプトトークン数 平均出力トークン数 Grafana、Metabase、Looker、Supersetなどで可視化します。 最低限、次の3つは作ってください。 機能別支出の時系列 顧客別支出の時系列 昨日の支出が多い上位20ルート これが、OpenAIダッシュボードの代わりに毎日見る運用ダッシュボードになります。 ラッパーのバグは静かにダッシュボードを壊します。特に危険なのは、ログが出ているように見えて、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開発も参考になります。 OpenAI側では、環境や主要機能ごとにプロジェクトキーを分けます。 例: prod-support-chat prod-summarization prod-agent staging-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_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呼び出しにも同じコスト帰属を適用し、イベントに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トークンを超えるリクエストに対して、入力に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-summarize queue:image-caption webhook: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プラットフォームも参照してください。 あるセールスインテリジェンス製品では、顧客がブリーフィングを要求するたびにGPT-5.5呼び出しが発生します。帰属なしでは、月8万ドルのOpenAI支出しか分かりません。 顧客ごとの帰属を入れると、顧客の12%が支出の71%を占めていることが分かりました。そこで段階的価格、ソフトクォータ、超過料金を導入し、AI機能の粗利益を改善できます。 エンジニア向けの社内GPTアシスタントでも同じです。customer_idに開発者メールを入れると、誰がどれだけ使っているかが分かります。 異常な支出を見つけることで、放置された自動エージェントループを停止できます。一方、正当な高利用者にはより高いクォータを割り当てる判断もできます。 新しい要約機能を出す前に、過去の機能別データから次を見積もります。 呼び出しあたりの平均入力トークン 呼び出しあたりの平均出力トークン アクティブユーザーあたりの想定呼び出し回数 想定アクティブユーザー数 これにより、機能単位の原価を事前に計算できます。価格設定やリリース可否の判断が推測ではなくなります。 測定できないものは管理できません。OpenAIの課金ダッシュボードは財務上の合計金額を示しますが、プロダクト運用には機能、顧客、ルートごとの帰属が必要です。 実装すべきことはシンプルです。 すべてのリクエストにfeature、route、customer_id、environmentを付ける OpenAIクライアントをラッパー経由に統一する トークン数とcost_usdを構造化ログで出力する ウェアハウスで集計する OpenAIプロジェクトキーごとに上限を設定する ウェアハウス側で異常検知する リリース前にApidogでラッパーを検証する Apidogをダウンロードして、コスト帰属ラッパーのE2E検証に使ってください。タグ付きリクエストでAIエンドポイントを実行し、ログペイロードの形状をアサートし、複数環境でシナリオを再生できます。 関連する読み物: GPT-5.5の価格内訳 APIチーム向けのGitHub Copilot利用料請求 出力レートで課金されます。OpenAI APIではusage.completion_tokens_details.reasoning_tokensとして返されるため、completion_tokensに加算してコスト計算してください。詳細はGPT-5.5の価格内訳を参照してください。 response.usageはOpenAIダッシュボードと一致しますか? トークン数はダッシュボードと一致します。ただし、古い料金表でコストを計算していると、価格変更によってずれます。モデルごとのレートはコードまたは設定で固定し、価格変更日に更新してください。 一部は可能です。プロジェクト単位の帰属や予算上限には有効です。ただし、機能、顧客、ルート単位の帰属にはアプリケーションレベルのメタデータが必要です。 モデル実行前に失敗したリクエストは通常usageを返さないため、コストは記録されません。成功後にアプリケーション側でリトライすると、request_idを再利用しない限り二重計上されます。冪等なリトライでは同じrequest_idを使い、書き込み時に重複排除してください。 リアルタイム監視には不向きです。数十分の遅延があります。アラートやキルスイッチには自分のログとウェアハウスを使い、月次調整にはusage APIを使うのが現実的です。 いいえ。リクエストごとに1行のJSONで済むため、データ量は小さいです。サンプリングすると顧客別、ルート別の正確な帰属が壊れます。すべて記録してください。 使えます。providerカラムを追加し、openai、anthropic、google、deepseekなどを入れます。プロバイダーごとに料金表とラッパーは変わりますが、ウェアハウスのスキーマとダッシュボードは共通化できます。比較としてDeepSeek V4 APIの価格設定も参照してください。 使えます。ただし、コスト計算はエンドポイントごとに分岐します。埋め込みは入力トークン単位、画像生成は画像枚数や解像度単位で計算します。スキーマにendpointを追加し、chat、embeddings、imageなどで分けてください。
