Read Usage Data

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

What Usage Data Is

Every successful Messages API response includes a usage object that reports how many tokens the request consumed:

message = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Explain Python decorators in two sentences."}],
)

print(message.usage.input_tokens)    # e.g. 17
print(message.usage.output_tokens)   # e.g. 112

Usage data is the ground truth for cost. API billing is based on tokens, so these numbers tell you exactly what each request cost. They also show how efficiently your application uses the model: how large your prompts are, how long the answers are, and whether optimizations such as prompt caching are working.


Why Usage Data Matters

UseWhat usage data tells you
Cost trackingThe exact cost of each request, user, feature, or day
Budgeting and limitsWhether a user or feature is approaching a spending limit
Performance tuningWhich prompts are unnecessarily large, and which answers are long
Conversation managementWhen history has grown enough to trim or summarize (Lesson 27)
Caching verificationWhether prompt caching actually saves tokens
Capacity planningToken volume against your organization's rate limits

The Usage Fields

usage = message.usage
FieldMeaning
input_tokensInput tokens processed normally, without caching: system prompt, messages, and other input not read from or written to the cache
output_tokensAll tokens the model generated, including thinking tokens that are not shown in the response
cache_creation_input_tokensInput tokens written to the prompt cache in this request
cache_read_input_tokensInput tokens read from the prompt cache in this request
cache_creationBreakdown of cache writes by lifetime: ephemeral_5m_input_tokens and ephemeral_1h_input_tokens
server_tool_useCounts of server-side tool requests, such as web_search_requests and web_fetch_requests
service_tierThe service tier that processed the request, such as "standard" or "batch"

Some features add further fields, such as a per-iteration breakdown when a request involves several internal steps. Fields that do not apply to a request may be absent or None, so read optional fields defensively.

Input Tokens Are Split Three Ways

When prompt caching is used, the input is divided among three fields:

total input tokens = input_tokens
                   + cache_creation_input_tokens
                   + cache_read_input_tokens

Without caching, the two cache fields are 0, and input_tokens is the whole input.

Why the split exists: each category has a different price. Normal input is billed at the base input price, cache writes cost more than normal input, and cache reads cost much less (see below). The API reports them separately so that you can calculate cost exactly.

Output Tokens Include Thinking

On current models, the model may think before answering (Lessons 13 and 16). Thinking tokens are billed as output and included in output_tokens, even when the thinking text is omitted from the response. The visible answer is therefore often shorter than output_tokens suggests. Always calculate cost from output_tokens, never from the length of the text.


Calculating Cost

The basic formula, with prices per million tokens (MTok):

cost = input_tokens               × input_price          / 1,000,000
     + cache_creation_tokens      × cache_write_price    / 1,000,000
     + cache_read_input_tokens    × cache_read_price     / 1,000,000
     + output_tokens              × output_price         / 1,000,000

Base prices for current models (USD per million tokens) at the time of writing:

ModelInputOutput
Claude Fable 5.1$10$50
Claude Opus 5.5$4$20
Claude Sonnet 5.5$2$10
Claude Haiku 5.5From $0.10From $0.50

Cache prices are multiples of the base input price:

Token typePrice
5-minute cache write1.25 × base input price
1-hour cache write2 × base input price
Cache readA small fraction of the base input price (0.1 × on most models; check the pricing page for each model's exact rate)

Example: a request on Claude Opus 5.5 with 1,000 normal input tokens and 500 output tokens:

1,000 × $4  / 1,000,000 = $0.004
  500 × $20 / 1,000,000 = $0.010
                  total = $0.014

Note: Prices change, and some features have their own pricing, such as the Message Batches API's 50 percent discount and charges per server-tool request. Always confirm current prices on the official pricing page, and keep prices in configuration rather than scattered through code.


Reading Usage Safely in Code

Core fields are always present on successful responses. Optional and nested fields may be missing or None. A small helper reads everything consistently:

def usage_summary(usage) -> dict[str, int]:
    """Return token counts as plain integers, treating missing values as 0."""
    return {
        "input": usage.input_tokens or 0,
        "output": usage.output_tokens or 0,
        "cache_write": getattr(usage, "cache_creation_input_tokens", 0) or 0,
        "cache_read": getattr(usage, "cache_read_input_tokens", 0) or 0,
    }
  • getattr(usage, "field", 0) returns 0 if the attribute does not exist, for example on older SDK versions.
  • or 0 converts a None value into 0.

The result is a simple dictionary of integers, easy to add up, store, or log.


Usage Across a Conversation

In multi-turn conversations, each request reports its own usage. Because the whole history is resent (Lesson 24), input_tokens grows with every turn:

Turn 1:  input=  20  output=120
Turn 2:  input= 160  output= 95
Turn 3:  input= 280  output=140
Turn 4:  input= 445  output=110

The total cost of a conversation is the sum over all requests, not the usage of the last one. Watching input_tokens per turn also tells you when to trim, summarize, or apply prompt caching.


Estimating Usage Before Sending

usage is reported after a request completes. To estimate input size before sending, for example to reject a huge document or choose a model, use the token counting endpoint, which does not generate a response:

count = client.messages.count_tokens(
    model="claude-opus-5-5",
    messages=[{"role": "user", "content": "Explain Python decorators in two sentences."}],
)
print(count.input_tokens)

Output tokens cannot be known in advance; max_tokens caps them. Lessons 44–46 cover token counting in detail.


Organization-Level Usage

Per-response usage is ideal for per-request and per-user tracking. For organization-wide reporting:

  • The Claude Console shows usage and cost for your organization and its workspaces.
  • The Admin API provides usage and cost reports programmatically. It requires an Admin API key, which is separate from a normal API key.

Practical Example: A Usage Tracker

This tracker records usage per model and per user, estimates cost, and prints a report. It is the basis of the cost monitoring covered in Lessons 185–186.

"""usage_tracker.py: record token usage per model and user, and estimate cost."""

from collections import defaultdict
from dataclasses import dataclass, field

from anthropic import Anthropic
from anthropic.types import Message

# USD per million tokens: (input, output). Verify against the official pricing page.
PRICES = {
    "claude-opus-5-5": (4.00, 20.00),
    "claude-sonnet-5-5": (2.00, 10.00),
}
CACHE_WRITE_MULTIPLIER = 1.25   # 5-minute cache writes
CACHE_READ_MULTIPLIER = 0.10    # Check the pricing page for each model's exact cache-read rate


@dataclass
class Totals:
    requests: int = 0
    input: int = 0
    output: int = 0
    cache_write: int = 0
    cache_read: int = 0

    def add(self, usage) -> None:
        self.requests += 1
        self.input += usage.input_tokens or 0
        self.output += usage.output_tokens or 0
        self.cache_write += getattr(usage, "cache_creation_input_tokens", 0) or 0
        self.cache_read += getattr(usage, "cache_read_input_tokens", 0) or 0

    def cost(self, model: str) -> float:
        input_price, output_price = PRICES.get(model, (0.0, 0.0))
        return (
            self.input * input_price
            + self.cache_write * input_price * CACHE_WRITE_MULTIPLIER
            + self.cache_read * input_price * CACHE_READ_MULTIPLIER
            + self.output * output_price
        ) / 1_000_000


@dataclass
class UsageTracker:
    by_model: dict[str, Totals] = field(default_factory=lambda: defaultdict(Totals))
    by_user: dict[str, dict[str, Totals]] = field(
        default_factory=lambda: defaultdict(lambda: defaultdict(Totals))
    )

    def record(self, message: Message, user_id: str) -> None:
        self.by_model[message.model].add(message.usage)
        self.by_user[user_id][message.model].add(message.usage)

    def user_cost(self, user_id: str) -> float:
        return sum(totals.cost(model) for model, totals in self.by_user[user_id].items())

    def report(self) -> None:
        grand_total = 0.0
        for model, totals in self.by_model.items():
            cost = totals.cost(model)
            grand_total += cost
            print(f"{model:<20} requests={totals.requests:<3} in={totals.input:<6} "
                  f"out={totals.output:<6} cache_r={totals.cache_read:<6} ~${cost:.4f}")
        print(f"{'TOTAL':<20} ~${grand_total:.4f}")


if __name__ == "__main__":
    client = Anthropic()
    tracker = UsageTracker()

    jobs = [
        ("student-1", "claude-opus-5-5", "Explain recursion in two sentences."),
        ("student-2", "claude-sonnet-5-5", "What is a Python tuple?"),
        ("student-1", "claude-sonnet-5-5", "Give one example of a list comprehension."),
    ]
    for user_id, model, prompt in jobs:
        reply = client.messages.create(
            model=model, max_tokens=1024, messages=[{"role": "user", "content": prompt}]
        )
        tracker.record(reply, user_id)

    tracker.report()
    print(f"student-1 spent ~${tracker.user_cost('student-1'):.4f}")

Example output (numbers vary):

claude-opus-5-5      requests=1   in=16     out=148    cache_r=0      ~$0.0030
claude-sonnet-5-5    requests=2   in=31     out=203    cache_r=0      ~$0.0021
TOTAL                ~$0.0051
student-1 spent ~$0.0040

How It Works

  1. PRICES maps model IDs to input and output prices in one place, so a price change means editing one line. Unknown models fall back to (0.0, 0.0) instead of crashing. In production, log a warning for unknown models instead.
  2. Totals.add() accumulates every usage field, reading optional cache fields defensively with getattr(...) or 0.
  3. Totals.cost() applies the cost formula: normal input at the base price, cache writes and reads at their multipliers, and output at the output price.
  4. defaultdict(Totals) creates an empty Totals the first time a model is seen, so record() needs no "does this key exist?" checks. by_user nests two levels, user and then model, because the same user may use different models with different prices.
  5. user_cost() adds up a user's cost across models. That is the number you need to enforce per-user limits (Lesson 179).
  6. report() prints one line per model and a grand total.

Common Mistakes

Estimating Cost from the Response Text

What happens: costs are underestimated, because thinking tokens are billed as output but not visible.

Fix: always use usage.output_tokens.

Ignoring Cache Fields

What happens: with prompt caching enabled, input_tokens looks small, and the cost calculation misses cache writes, or applies the wrong price to cache reads.

Fix: include cache_creation_input_tokens and cache_read_input_tokens, each with its own price.

Using Only the Last Request's Usage for a Conversation

What happens: the reported cost covers one turn, not the whole conversation.

Fix: sum the usage of every request in the conversation.

Hardcoding Prices Throughout the Code

What happens: price changes require edits in many places, and some are missed.

Fix: keep prices in one configuration structure, and verify them against the official pricing page.

Crashing on Missing Optional Fields

What happens: code that reads usage.cache_creation.ephemeral_1h_input_tokens fails when cache_creation is None.

Fix: read optional and nested fields defensively.


Best Practices

  • Record usage for every request, together with the message ID, model, user, and feature (Lesson 29).
  • Calculate cost from all usage fields, including cache fields and output tokens with thinking.
  • Aggregate by user, feature, and model to find where cost comes from.
  • Use count_tokens() to check large inputs before sending them.
  • Keep prices in configuration and review them when models or prices change.
  • Watch input-token growth in conversations, and trim or cache accordingly.
  • Compare your totals with the Console periodically to confirm your tracking is accurate.

Exercise

  1. Send three requests of different lengths and print the usage_summary() of each.
  2. Add claude-fable-5-1 with prices (10.00, 50.00) to PRICES, and include it in the tracker's jobs. How does its cost compare?
  3. Extend UsageTracker with a limit_exceeded(user_id, limit_usd) method that returns True when a user's estimated cost exceeds a limit.
  4. Run a four-turn conversation (Lesson 24) and record each turn in the tracker. Compare the total input tokens with the input tokens of the last turn alone, and explain the difference.
Show answers for steps 3 and 4

Step 3:

def limit_exceeded(self, user_id: str, limit_usd: float) -> bool:
    return self.user_cost(user_id) > limit_usd

Add it to the UsageTracker class. An application can check it before sending each request, and refuse or downgrade the request when the limit is reached.

Step 4: the total is much larger than the last turn's input tokens, because every turn resent all previous turns. The last turn alone includes the full history once, but the total includes the history once per turn: the first turn's text is paid for four times, the second turn's three times, and so on. This is why long conversations benefit from trimming, summarizing, and prompt caching.

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 Read Usage Data and get answers drawn from it.

Signed-in readers only.