Track API Costs

Su Suriya Ravichandran Updated 10 Nov 2026
5 min read ·Lesson 186 of 200

From Tokens to Money

Lesson 185 measured tokens. A team cares about money, and money depends on the tokens multiplied by the price of each kind of token, for each model, on each day. The same token count costs very different amounts on different models, and output tokens are priced higher than input tokens. A dashboard that reports only tokens can hide a feature whose small volume runs on the most expensive model, and a dashboard that reports only the total bill cannot say which feature caused it.

This lesson covers how to turn usage records into cost, how to attribute cost to features and days, how to compare the application's estimate with the invoice, and how to set budget alarms that fire before a bill surprises anyone. The prices in the examples are examples: the current figures come from the pricing page, and a table in code must be updated when they change.


Cost Is Price Times Usage, Per Kind

Each call is billed for its input tokens and its output tokens, at prices that depend on the model. Cache reads and writes carry their own prices, as Lessons 125 and 126 described. The cost of one call is therefore a sum of products, and the price table needs one entry per model with one price per kind of token:

PRICES_PER_MILLION = {
    "claude-sonnet-5-5": {"input": 2.00, "output": 10.00},
    "claude-opus-5-5": {"input": 4.00, "output": 20.00},
}


def call_cost(model: str, input_tokens: int, output_tokens: int) -> float:
    prices = PRICES_PER_MILLION[model]
    return (input_tokens * prices["input"] + output_tokens * prices["output"]) / 1_000_000

The function fails loudly for an unknown model, through the dictionary lookup, rather than assuming a price. An unknown model in production should stop the cost report and raise an alarm, because a silent default would understate spending.


Attributing Cost

Attribution connects each cost to the feature that caused it, and to the day it was incurred. Those two labels are what turn a total into a decision. A daily report by feature shows which feature grew, which model it uses, and whether its spending matches its value.

The report below groups call records by day and feature, totals the cost, and compares each day's spending with a budget. Each record carries the model, so the cost of a feature is correct even when it uses more than one model:

"""cost_report.py: attribute call costs to days and features, and alarm on budget overruns."""

from collections import defaultdict
from dataclasses import dataclass

PRICES_PER_MILLION = {
    "claude-sonnet-5-5": {"input": 2.00, "output": 10.00},
    "claude-opus-5-5": {"input": 4.00, "output": 20.00},
}


@dataclass(frozen=True)
class CallRecord:
    day: str
    feature: str
    model: str
    input_tokens: int
    output_tokens: int


def call_cost(record: CallRecord) -> float:
    prices = PRICES_PER_MILLION[record.model]
    return (record.input_tokens * prices["input"] + record.output_tokens * prices["output"]) / 1_000_000


def daily_report(records: list[CallRecord], daily_budget: float) -> list[str]:
    by_day: dict[str, dict[str, float]] = defaultdict(lambda: defaultdict(float))
    for record in records:
        by_day[record.day][record.feature] += call_cost(record)
    lines = []
    for day, features in by_day.items():
        for feature, total in features.items():
            lines.append(f"{day} {feature}: ${total:.2f}")
        day_total = sum(features.values())
        lines.append(f"{day} total: ${day_total:.2f}")
        if day_total > daily_budget:
            lines.append(f"alert: {day} spend ${day_total:.2f} is over the ${daily_budget:.2f} budget")
    return lines


if __name__ == "__main__":
    records = [
        CallRecord("2026-11-09", "summarize", "claude-sonnet-5-5", 1_000_000, 100_000),
        CallRecord("2026-11-09", "classify", "claude-sonnet-5-5", 2_000_000, 50_000),
        CallRecord("2026-11-09", "tutor", "claude-opus-5-5", 500_000, 200_000),
        CallRecord("2026-11-10", "summarize", "claude-sonnet-5-5", 1_500_000, 150_000),
    ]
    for line in daily_report(records, daily_budget=10.00):
        print(line)

Output:

2026-11-09 summarize: $3.00
2026-11-09 classify: $4.50
2026-11-09 tutor: $6.00
2026-11-09 total: $13.50
alert: 2026-11-09 spend $13.50 is over the $10.00 budget
2026-11-10 total: $4.50

How It Works

  1. Each record's cost comes from its own model's price. The tutor feature runs on the more expensive model, so its cost is computed with that model's prices, and the report reflects it.
  2. The report is grouped by day, then by feature. The first day's three features add up to the day total, which is compared with the budget.
  3. The alert fires on the day that exceeds the budget. The second day, at $4.50, stays under the limit and produces no alert.
  4. The arithmetic is exact for these inputs. The summarize feature costs one million input tokens at $2 plus one hundred thousand output tokens at $10 per million, which is $2 plus $1, or $3.00.
  5. The budget is a parameter, so the same report serves different limits for different environments.

Reconciling With the Invoice

The application's cost figure is an estimate built from its own records and its own price table. The invoice is the authority, and the two can differ: a price table may be out of date, a record may be missing, a retry may have been billed that the application did not record, or a discount may apply. Reconciliation compares the two over a period and investigates the difference. A small, stable difference is expected, and a growing one means a record or a price is wrong.

The platform's usage and cost reporting, which the documentation describes, provides the figures to compare against. The comparison should run regularly, not only at billing time, because a discrepancy found early is much easier to explain.


Budget Alarms

An alarm is worth more when it fires early. A budget of ten dollars for a day is a limit, and an alarm at eighty percent of it gives time to act before the limit is reached. The alarm should name the feature responsible, since the response often depends on which feature grew. Lesson 179 described the per-user budget that enforces spending in code, and the alarms here are the warning that a person sees before that enforcement becomes necessary.


Keeping the Price Table Current

The price table is data that goes stale. Prices change, new models appear, and cache and batch rates differ from standard rates. A table that is not reviewed produces reports that are confidently wrong. A practical habit is to record the date the table was checked, to keep the table in configuration rather than in scattered code, and to reconcile against the invoice whenever prices are updated. Batch usage (Lesson 131) and cache usage (Lesson 125) need their own rates in the table, or their costs are misstated.


Summary

Cost is the sum of each token kind's count times its price, for the model used. Attributing cost to days and features turns a total into a decision, and the report should compare each day with a budget and alarm early. The application's estimate must be reconciled with the invoice regularly, because the price table and the records can both drift. Alarms should fire before limits, and name the feature responsible. The next lesson covers the logs that record how the application runs, which is the subject of configuring logging.

0 Comments

Reviewed before they appear

No comments yet.

Anthropic SDK Using Python
Anthropic SDK Intro Claude API Intro SDK Requirements Install Anthropic SDK Upgrade the SDK Create an API Key Set API Keys Use Environment Variables Import Anthropic Create a Client Send Your First Message Choose a Claude Model Set Max Tokens Read a Response Print Response Text Understand Message Blocks Use System Prompts Send User Messages Set Temperature Handle Stop Reasons Messages API Intro Create Messages Use Message Roles Send Conversation History Continue a Conversation Build a Chat Loop Reset Chat History Use Multiple Turns Inspect Message IDs Read Usage Data Write Clear Prompts Set a System Prompt Add Context to Prompts Use Prompt Templates Add Examples to Prompts Request JSON Output Use XML Tags Control Response Length Improve Prompt Quality Avoid Prompt Injection Read Text Blocks Read Content Blocks Check Stop Reasons Count Input Tokens Count Output Tokens Count Tokens Before Sending Limit Output Tokens Use Stop Sequences Read Request IDs Parse API Responses Streaming Intro Stream Text Output Use Stream Events Use Stream Helpers Get Final Messages Stream Async Responses Handle Stream Errors Build a Live Chat Stream Long Responses Close Streams Safely Build a Chatbot Keep Chat History Manage Chat Sessions Add Chat Memory Build a CLI Chatbot Build a Web Chatbot Use Flask with Claude Use FastAPI with Claude Return JSON from an API Add Chat Error Messages Async SDK Intro Create an Async Client Send Async Messages Use asyncio Run Parallel Requests Stream Async Text Handle Async Errors Set Async Timeouts Close Async Clients Choose Sync or Async Tool Use Intro Define a Tool Create Tool Schemas Send Tools to Claude Read Tool Calls Run Python Tools Return Tool Results Continue After Tool Use Control Tool Choice Handle Tool Errors Use the beta_tool Decorator Create a Tool Runner Add Tool Docstrings Validate Tool Inputs Use Multiple Tools Limit Tool Calls Log Tool Activity Build a Weather Tool Build a Search Tool Build a Calculator Tool Structured Output Intro Request JSON Data Validate JSON Output Use Pydantic Models Handle Invalid JSON Extract Fields from Text Generate Typed Data Use Image URLs Send Base64 Images Read PDFs with Claude Use Output Schemas Build Data Extraction Test Structured Responses Send Image Inputs Upload Files Use File IDs Process Text Files Summarize Documents Extract Table Data Handle File Errors Read Cache Usage Reduce Repeated Input Costs Debug Cache Misses Prompt Caching Intro Enable Prompt Caching Cache System Prompts Cache Long Context Test Cache Performance Use Cache Breakpoints Cache Best Practices Message Batches Intro Create a Message Batch Set Batch IDs Check Batch Status List Message Batches Get Batch Results Process Batch Outputs Handle Failed Batch Items Use Batches for Bulk Tasks Compare Batches and Requests Handle API Errors Handle Rate Limits Retry Failed Requests Set Request Timeouts Handle Network Errors Use Exponential Backoff Build Reliable API Calls Use Extended Thinking Log API Errors Track Request IDs Paginate API Results Set Thinking Budgets Read Thinking Blocks Use Tool Search Use Web Search Tools Use Computer Tools Use Citations Use Token Counting Read Raw Responses Set Custom Headers Use Claude on AWS Bedrock Use Claude on Vertex AI Use Claude on Microsoft Foundry Configure Cloud Credentials Choose a Cloud Provider Compare API Integrations Configure Provider Clients Handle Cloud Errors Use Cloud SDK Extras Deploy Cloud Apps Protect API Keys Use a .env File Ignore Secrets in Git Validate User Input Reduce Prompt Injection Limit Tool Permissions Avoid Unsafe Tool Calls Sanitize Tool Results Set Usage Limits Review Security Logs Test SDK Functions Mock API Responses Test Error Handling Test Streaming Code Measure Token Usage Track API Costs Configure Logging Deploy with Docker Deploy a FastAPI App Monitor Production Apps Build a Claude Chatbot Build a Text Summarizer Build a Code Explainer Build a JSON Extractor Build a PDF Assistant Build a Streaming Chat App Build a Tool-Calling Agent Build a Batch Processor Build a Research Assistant Build a Claude API Dashboard
Ask about this post
AI Ask about this post

Ask questions about Track API Costs and get answers drawn from it.

Signed-in readers only.