CLAUDE LABEN
FORK — Claude Code 2.1.212で/forkの挙動が変わりました。会話を新しいバックグラウンドセッションへ複製し、作業を続けたまま並走できます。従来のセッション内サブエージェントは/subtaskに移りましたLIMITS — WebSearchの呼び出しがセッション単位で既定200回に制限されました。サブエージェントの起動も既定200回が上限で、暴走した検索・委譲のループを止められますMCPBG — 2分を超えるMCPツール呼び出しは自動的にバックグラウンドへ移り、セッションが固まらなくなりました。しきい値はCLAUDE_CODE_MCP_AUTO_BACKGROUND_MSで調整できますPLANFIX — プランモードがtouchやrmといったファイルを変更するBashコマンドを、許可プロンプトもcanUseToolコールバックも通さずに実行してしまう不具合が修正されましたSONNET5 — Claude Sonnet 5は導入価格として入力100万トークンあたり2ドル、出力10ドルで提供中です。8月31日を過ぎると3ドルと15ドルに戻りますIPO — Anthropicが早ければ10月の株式公開を視野に、引受銀行が投資家との面談を組み始めたと報じられていますFORK — Claude Code 2.1.212で/forkの挙動が変わりました。会話を新しいバックグラウンドセッションへ複製し、作業を続けたまま並走できます。従来のセッション内サブエージェントは/subtaskに移りましたLIMITS — WebSearchの呼び出しがセッション単位で既定200回に制限されました。サブエージェントの起動も既定200回が上限で、暴走した検索・委譲のループを止められますMCPBG — 2分を超えるMCPツール呼び出しは自動的にバックグラウンドへ移り、セッションが固まらなくなりました。しきい値はCLAUDE_CODE_MCP_AUTO_BACKGROUND_MSで調整できますPLANFIX — プランモードがtouchやrmといったファイルを変更するBashコマンドを、許可プロンプトもcanUseToolコールバックも通さずに実行してしまう不具合が修正されましたSONNET5 — Claude Sonnet 5は導入価格として入力100万トークンあたり2ドル、出力10ドルで提供中です。8月31日を過ぎると3ドルと15ドルに戻りますIPO — Anthropicが早ければ10月の株式公開を視野に、引受銀行が投資家との面談を組み始めたと報じられています
記事一覧/API & SDK
API & SDK/2026-04-25上級

Claude APIサービスに使用量課金を実装する — トークン計測・価格換算・Stripe Metering実装ガイド

Claude APIを組み込んだサービスで使用量に応じた課金を実装する方法を完全解説。トークン計測から価格換算ロジック、Stripe Metered Billingとの統合、利用制限の実装まで動作確認済みコードで紹介します。

Claude API115Stripe15使用量課金SaaS14課金実装2収益化25Metered Billing

Claude APIを組み込んだサービスを作るとき、一番悩むのが課金モデルの設計ではないでしょうか。

月額固定プランはシンプルですが、ヘビーユーザーに吸われてコストが膨らむリスクがあります。従量制は公平ですが、ユーザーが使い方を読みにくく、チャーンにつながりやすい。その中間にある**使用量課金(Usage-Based Billing)**は、両者の良いところを取れる設計ですが、実装が複雑です。

ここではClaude APIのトークン計測から始まり、Stripe Metered Billingとの統合、ユーザーごとの利用制限まで、実際に動くコードと設計判断を丸ごと公開します。

使用量課金の設計思想:なぜフラットレートではないのか

使用量課金を選ぶ理由は、主に2つです。

コストとの整合性: Claude APIはトークン単位で課金されます。固定月額でサービスを提供すると、ヘビーユーザーがコストを食いつぶす構造になりがちです。使用量に比例した課金にすることで、マージンが安定します。

ユーザーにとっての公平感: 月に10回しか使わないユーザーと1,000回使うユーザーが同じ料金を払う構造は、前者が離脱する動機になります。使用量ベースにすることで、ライトユーザーを失わずに済みます。

課題は「何をどの単位で課金するか」の設計です。私がよく使うアプローチはクレジット制です。1クレジット = ¥1として内部管理し、Claude APIのコストに一定のマークアップを乗せてクレジットに換算します。ユーザーにはクレジットを購入してもらい、API呼び出しごとに消費させます。

Step 1: トークン使用量の計測とログ記録

Claude APIのレスポンスにはusageフィールドが含まれており、ここからinput/outputトークン数を取得できます。

import anthropic
import logging
from dataclasses import dataclass
from datetime import datetime, UTC
 
client = anthropic.Anthropic()
 
# ログ設定(本番環境ではCloud Logging等に出力)
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
 
@dataclass
class UsageRecord:
    """1回のAPI呼び出しの使用量記録"""
    user_id: str
    model: str
    input_tokens: int
    output_tokens: int
    cache_creation_tokens: int  # プロンプトキャッシュ作成
    cache_read_tokens: int      # プロンプトキャッシュ読み取り
    timestamp: datetime
    request_id: str
 
# モデルごとの価格(USD per 1M tokens)
MODEL_PRICING = {
    "claude-opus-4-6": {
        "input": 15.00,
        "output": 75.00,
        "cache_creation": 18.75,  # input × 1.25
        "cache_read": 1.50,       # input × 0.10
    },
    "claude-sonnet-4-6": {
        "input": 3.00,
        "output": 15.00,
        "cache_creation": 3.75,
        "cache_read": 0.30,
    },
    "claude-haiku-4-5-20251001": {
        "input": 0.80,
        "output": 4.00,
        "cache_creation": 1.00,
        "cache_read": 0.08,
    },
}
 
# JPY換算レート(本番では為替APIから取得推奨)
USD_TO_JPY = 148.0
 
# サービスのマークアップ倍率(2.5倍 = コストの2.5倍を顧客に請求)
MARKUP_FACTOR = 2.5
 
def calculate_cost_jpy(record: UsageRecord) -> float:
    """
    UsageRecordからJPYコスト(顧客請求額)を計算する。
    マークアップ込みの価格を返す。
    """
    pricing = MODEL_PRICING.get(record.model)
    if not pricing:
        raise ValueError(f"Unknown model: {record.model}")
    
    # USD単価(per 1M tokens)から実コストを計算
    input_cost_usd = (record.input_tokens / 1_000_000) * pricing["input"]
    output_cost_usd = (record.output_tokens / 1_000_000) * pricing["output"]
    cache_create_usd = (record.cache_creation_tokens / 1_000_000) * pricing["cache_creation"]
    cache_read_usd = (record.cache_read_tokens / 1_000_000) * pricing["cache_read"]
    
    total_cost_usd = input_cost_usd + output_cost_usd + cache_create_usd + cache_read_usd
    total_cost_jpy = total_cost_usd * USD_TO_JPY
    
    # マークアップ適用(1円未満は切り上げ)
    import math
    billable_jpy = math.ceil(total_cost_jpy * MARKUP_FACTOR)
    
    logger.info(
        "usage_calculated",
        extra={
            "user_id": record.user_id,
            "model": record.model,
            "tokens_in": record.input_tokens,
            "tokens_out": record.output_tokens,
            "cost_usd": round(total_cost_usd, 6),
            "billable_jpy": billable_jpy,
        }
    )
    
    return billable_jpy
 
def call_claude_with_tracking(
    user_id: str,
    messages: list[dict],
    model: str = "claude-sonnet-4-6",
    system: str = "",
) -> tuple[str, UsageRecord]:
    """
    Claude APIを呼び出し、レスポンスと使用量記録を返す。
    呼び出し元はUsageRecordをDBに保存し、Stripeに報告する責任を持つ。
    """
    import uuid
    request_id = str(uuid.uuid4())
    
    kwargs = {
        "model": model,
        "max_tokens": 4096,
        "messages": messages,
    }
    if system:
        kwargs["system"] = system
    
    response = client.messages.create(**kwargs)
    
    usage = response.usage
    record = UsageRecord(
        user_id=user_id,
        model=model,
        input_tokens=usage.input_tokens,
        output_tokens=usage.output_tokens,
        cache_creation_tokens=getattr(usage, 'cache_creation_input_tokens', 0),
        cache_read_tokens=getattr(usage, 'cache_read_input_tokens', 0),
        timestamp=datetime.now(UTC),
        request_id=request_id,
    )
    
    return response.content[0].text, record
 
# 使用例
text, record = call_claude_with_tracking(
    user_id="user_abc123",
    messages=[{"role": "user", "content": "Pythonでシンプルなスタックを実装してください"}],
    model="claude-sonnet-4-6",
)
cost_jpy = calculate_cost_jpy(record)
print(f"トークン: {record.input_tokens}入力 / {record.output_tokens}出力")
print(f"課金額(マークアップ込み): ¥{cost_jpy}")

このコードの設計で重要なのは、call_claude_with_trackingが使用量記録(UsageRecord)を返すだけで、DBへの保存やStripeへの報告は呼び出し元の責任にしていることです。関心の分離によって、テストが書きやすくなります。

Step 2: PostgreSQLでの使用量記録

使用量を正確に記録するためのDBスキーマです。

-- 使用量記録テーブル
CREATE TABLE api_usage_records (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id VARCHAR(255) NOT NULL,
    request_id VARCHAR(255) UNIQUE NOT NULL,
    model VARCHAR(100) NOT NULL,
    input_tokens INTEGER NOT NULL DEFAULT 0,
    output_tokens INTEGER NOT NULL DEFAULT 0,
    cache_creation_tokens INTEGER NOT NULL DEFAULT 0,
    cache_read_tokens INTEGER NOT NULL DEFAULT 0,
    cost_jpy INTEGER NOT NULL DEFAULT 0,  -- 円単位(整数)
    stripe_reported BOOLEAN NOT NULL DEFAULT FALSE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
 
CREATE INDEX idx_usage_user_id ON api_usage_records(user_id);
CREATE INDEX idx_usage_created_at ON api_usage_records(created_at);
CREATE INDEX idx_usage_stripe_reported ON api_usage_records(stripe_reported) WHERE stripe_reported = FALSE;
 
-- ユーザーの月次使用量集計ビュー
CREATE VIEW monthly_usage_by_user AS
SELECT
    user_id,
    DATE_TRUNC('month', created_at) AS month,
    SUM(input_tokens) AS total_input_tokens,
    SUM(output_tokens) AS total_output_tokens,
    SUM(cost_jpy) AS total_cost_jpy,
    COUNT(*) AS request_count
FROM api_usage_records
GROUP BY user_id, DATE_TRUNC('month', created_at);

cost_jpyを整数(円単位)で保存しているのは、浮動小数点の計算誤差を避けるためです。金額を扱うテーブルでは、最小単位の整数で保存する方が安全です。

Step 3: Stripe Metered Billingとの統合

Stripeの使用量課金(Metered Billing)を使うと、月末に使用量を自動集計して請求できます。

import stripe
import psycopg2
from datetime import datetime, UTC
 
stripe.api_key = "sk_live_..."  # 実際はシークレット管理ツールから取得
 
def report_usage_to_stripe(
    stripe_subscription_item_id: str,
    user_id: str,
    db_conn
) -> int:
    """
    未報告の使用量をStripeに送信する。
    バックグラウンドジョブから定期的に呼び出すことを想定。
    
    Returns: 報告した件数
    """
    with db_conn.cursor() as cur:
        # 未報告のレコードを取得(バッチサイズ上限100)
        cur.execute("""
            SELECT id, cost_jpy, created_at
            FROM api_usage_records
            WHERE user_id = %s
              AND stripe_reported = FALSE
            ORDER BY created_at ASC
            LIMIT 100
            FOR UPDATE SKIP LOCKED
        """, (user_id,))
        
        records = cur.fetchall()
        if not records:
            return 0
        
        # 使用量の合計(Stripeには集計値で送信する方が効率的)
        total_cost_jpy = sum(r[1] for r in records)
        record_ids = [r[0] for r in records]
        
        try:
            # Stripeに使用量を報告
            stripe.SubscriptionItem.create_usage_record(
                stripe_subscription_item_id,
                quantity=total_cost_jpy,  # ¥1単位で送信
                timestamp=int(datetime.now(UTC).timestamp()),
                action="increment",  # 加算モード
            )
            
            # DBを更新(stripe_reported = TRUE)
            cur.execute("""
                UPDATE api_usage_records
                SET stripe_reported = TRUE
                WHERE id = ANY(%s)
            """, (record_ids,))
            
            db_conn.commit()
            return len(records)
            
        except stripe.error.StripeError as e:
            db_conn.rollback()
            # エラーはログに記録し、次回のバッチで再試行
            logger.error(f"Stripe usage report failed: {e}", extra={"user_id": user_id})
            raise
 
def setup_stripe_metered_product() -> dict:
    """
    Stripeで使用量課金商品をセットアップする(初回1回だけ実行)。
    返り値にprice_idとproduct_idが含まれる。
    """
    # 商品作成
    product = stripe.Product.create(
        name="AI APIサービス(使用量課金)",
        description="Claude API使用量に応じた従量課金プラン",
    )
    
    # 価格(Metered Billing)作成
    price = stripe.Price.create(
        product=product.id,
        currency="jpy",
        billing_scheme="per_unit",
        unit_amount=1,  # ¥1/クレジット
        recurring={
            "interval": "month",
            "usage_type": "metered",  # 使用量課金
            "aggregate_usage": "sum",  # 月内の合計値で請求
        },
    )
    
    return {"product_id": product.id, "price_id": price.id}

action="increment"を使っているのは、複数のバッチ処理が並行して実行されたときに二重計上を防ぐためです。set(絶対値指定)よりincrement(加算)の方が冪等性を保ちやすい場面が多いです。

Step 4: ユーザーごとのプランと利用制限

使用量課金でも、プランによって上限を設けることはよくある設計です。

from enum import Enum
from typing import Optional
import redis
 
r = redis.Redis(host='localhost', port=6379, db=0)
 
class PlanTier(str, Enum):
    FREE = "free"
    STARTER = "starter"    # ¥980/月
    PRO = "pro"            # ¥3,800/月
    ENTERPRISE = "enterprise"  # 個別契約
 
# プランごとの月間クレジット上限
PLAN_LIMITS = {
    PlanTier.FREE: 500,          # ¥500相当
    PlanTier.STARTER: 5_000,     # ¥5,000相当(¥980の売上に対してコスト管理)
    PlanTier.PRO: 30_000,        # ¥30,000相当
    PlanTier.ENTERPRISE: None,   # 無制限(別途交渉)
}
 
# プランごとの使用可能モデル
PLAN_ALLOWED_MODELS = {
    PlanTier.FREE: ["claude-haiku-4-5-20251001"],
    PlanTier.STARTER: ["claude-haiku-4-5-20251001", "claude-sonnet-4-6"],
    PlanTier.PRO: ["claude-haiku-4-5-20251001", "claude-sonnet-4-6", "claude-opus-4-6"],
    PlanTier.ENTERPRISE: ["claude-haiku-4-5-20251001", "claude-sonnet-4-6", "claude-opus-4-6"],
}
 
class UsageLimitExceeded(Exception):
    """使用量上限超過エラー"""
    def __init__(self, limit: int, current: int, plan: PlanTier):
        self.limit = limit
        self.current = current
        self.plan = plan
        super().__init__(f"月間上限 ¥{limit} に達しました(現在 ¥{current})")
 
class ModelNotAllowed(Exception):
    """プランで許可されていないモデルの使用"""
    def __init__(self, model: str, plan: PlanTier):
        self.model = model
        self.plan = plan
        super().__init__(f"モデル {model}{plan.value} プランでは使用できません")
 
def get_monthly_usage_jpy(user_id: str) -> int:
    """Redisから今月の累計使用量を取得(高速キャッシュ)"""
    now = datetime.now(UTC)
    cache_key = f"usage:{user_id}:{now.year}:{now.month}"
    
    cached = r.get(cache_key)
    if cached is not None:
        return int(cached)
    
    # キャッシュミス時はDBから取得してキャッシュ
    # (実際はDB接続を引数で受け取るか依存注入する)
    usage = 0  # DB問い合わせ結果
    r.setex(cache_key, 3600, usage)  # 1時間キャッシュ
    return usage
 
def increment_monthly_usage_cache(user_id: str, cost_jpy: int) -> int:
    """使用量をRedisに加算し、新しい合計を返す"""
    now = datetime.now(UTC)
    cache_key = f"usage:{user_id}:{now.year}:{now.month}"
    
    new_total = r.incrby(cache_key, cost_jpy)
    # TTLが設定されていなければ設定(月末まで)
    if r.ttl(cache_key) == -1:
        import calendar
        days_in_month = calendar.monthrange(now.year, now.month)[1]
        seconds_until_eom = (days_in_month - now.day + 1) * 86400
        r.expire(cache_key, seconds_until_eom)
    
    return new_total
 
def check_and_execute(
    user_id: str,
    plan: PlanTier,
    model: str,
    messages: list[dict],
    system: str = "",
) -> tuple[str, int]:
    """
    プランチェック → 上限確認 → API呼び出し → 使用量記録
    を一連で実行する。APIレスポンスと課金額(JPY)を返す。
    
    Raises:
        ModelNotAllowed: プランで許可されていないモデルを指定した場合
        UsageLimitExceeded: 月間上限を超えた場合
    """
    # モデル許可チェック
    if model not in PLAN_ALLOWED_MODELS[plan]:
        raise ModelNotAllowed(model, plan)
    
    # 上限チェック(上限ありのプランのみ)
    plan_limit = PLAN_LIMITS[plan]
    if plan_limit is not None:
        current_usage = get_monthly_usage_jpy(user_id)
        if current_usage >= plan_limit:
            raise UsageLimitExceeded(plan_limit, current_usage, plan)
    
    # API呼び出し
    response_text, record = call_claude_with_tracking(
        user_id=user_id,
        messages=messages,
        model=model,
        system=system,
    )
    
    # 課金額計算
    cost_jpy = calculate_cost_jpy(record)
    
    # Redisキャッシュ更新(非同期でDBとStripeにも保存すること)
    new_total = increment_monthly_usage_cache(user_id, cost_jpy)
    
    logger.info(f"Usage: user={user_id}, cost=¥{cost_jpy}, monthly_total=¥{new_total}")
    
    return response_text, cost_jpy

よくある落とし穴3つ

実際に使用量課金を実装して気づいた問題を正直に書きます。

1. Stripeへの二重報告

バックグラウンドジョブが並行して動作したとき、同じレコードをStripeに2回報告してしまうことがあります。解決策はStep 2で示したようにFOR UPDATE SKIP LOCKEDを使い、取得中のレコードを他のジョブがスキップするようにすることです。

2. 為替レート固定の危険性

USD_TO_JPY = 148.0をハードコードしていますが、為替が10%変動するとマージンが大きくブレます。月次で為替APIから取得して更新する仕組みを入れることを強くお勧めします。exchangerate.hostopenexchangerates.orgが使いやすいです。

3. Redisとの整合性

Redisのキャッシュが正確でない場合(Redis障害・再起動など)、実際の使用量より多くまたは少なく見えることがあります。毎日未明にRedisをDBから再構築するバッチジョブを入れておくと、長期的な整合性が保てます。

応用例:段階的プランのアップセル設計

上限に近づいたユーザーへのアップセルは、使用量課金の最大の強みです。

def get_upgrade_nudge(user_id: str, plan: PlanTier) -> Optional[str]:
    """
    上限の80%を超えたら、アップグレード促進メッセージを返す。
    APIレスポンスに付与して表示する。
    """
    plan_limit = PLAN_LIMITS.get(plan)
    if plan_limit is None:
        return None  # Enterpriseは上限なし
    
    current = get_monthly_usage_jpy(user_id)
    usage_ratio = current / plan_limit
    
    if usage_ratio >= 0.95:
        return f"今月の利用上限まで残り ¥{plan_limit - current}。Proプランへアップグレードすると上限が ¥{PLAN_LIMITS[PlanTier.PRO]:,} に拡大されます。"
    elif usage_ratio >= 0.80:
        return f"今月の利用量が上限の{int(usage_ratio * 100)}%に達しました。"
    
    return None

この関数をAPIレスポンスのメタデータに含めておけば、フロントエンドでバナーを出すだけで自然なアップセルが実現します。

実装順序のまとめ

設計が複雑に見えますが、実装順序を整理すると次のようになります。まずStep 1とStep 2でトークン計測とDB記録を先に作り、実際のコストデータを蓄積します。次にStep 4の上限チェックを入れ、コストが無制限にならない安全弁を先に作ります。最後にStep 3のStripe Metered Billingを組み込み、実際の請求を自動化します。

最初から全部を完璧に作ろうとしないことが、リリースを早める秘訣です。計測だけ先にできていれば、課金の仕組みは後から追加できます。

シェア

お読みいただきありがとうございます

Claude Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

API & SDK2026-04-27
個人開発者がClaude APIで月収を作る完全ロードマップ — 90日で『払う人』にたどり着く実装と運用の全工程
Claude API を使った個人収益化を、アイデア出しからStripe統合、SEO、サブスク化、運用の心の管理までを90日のロードマップでまとめました。私が個人アプリ開発で12年やってきた経験と、AI API時代ならではの落とし穴を踏まえた、あえて遠回りしないための完全ガイドです。
API & SDK2026-04-24
Claude API × MCP で構築する有料コンサルティング SaaS の実装
個人で運営する有料コンサルティング SaaS の設計・実装・運用を完全解説。Claude API × MCP × Stripe × D1 KV を組み合わせた月10~30万円規模の収益構造、トークン従量課金の仕組み、実装コード、そして月商30万円まで成長させた際の学んだ実問題への対処法をお伝えします。
API & SDK2026-04-04
Claude API で作る フルスタック AI SaaS 収益化設計図 2026 — 設計から自動課金まで
Claude API を核とした AI SaaS を個人開発で収益化するための完全設計図。アーキテクチャ設計・Stripe 課金実装・コスト最適化・スケール戦略まで、実際のコードとともに詳しく解説します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →