Read Usage Data
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
| Use | What usage data tells you |
|---|---|
| Cost tracking | The exact cost of each request, user, feature, or day |
| Budgeting and limits | Whether a user or feature is approaching a spending limit |
| Performance tuning | Which prompts are unnecessarily large, and which answers are long |
| Conversation management | When history has grown enough to trim or summarize (Lesson 27) |
| Caching verification | Whether prompt caching actually saves tokens |
| Capacity planning | Token volume against your organization's rate limits |
The Usage Fields
usage = message.usage
| Field | Meaning |
|---|---|
input_tokens | Input tokens processed normally, without caching: system prompt, messages, and other input not read from or written to the cache |
output_tokens | All tokens the model generated, including thinking tokens that are not shown in the response |
cache_creation_input_tokens | Input tokens written to the prompt cache in this request |
cache_read_input_tokens | Input tokens read from the prompt cache in this request |
cache_creation | Breakdown of cache writes by lifetime: ephemeral_5m_input_tokens and ephemeral_1h_input_tokens |
server_tool_use | Counts of server-side tool requests, such as web_search_requests and web_fetch_requests |
service_tier | The 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:
| Model | Input | Output |
|---|---|---|
| Claude Fable 5.1 | $10 | $50 |
| Claude Opus 5.5 | $4 | $20 |
| Claude Sonnet 5.5 | $2 | $10 |
| Claude Haiku 5.5 | From $0.10 | From $0.50 |
Cache prices are multiples of the base input price:
| Token type | Price |
|---|---|
| 5-minute cache write | 1.25 × base input price |
| 1-hour cache write | 2 × base input price |
| Cache read | A 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)returns0if the attribute does not exist, for example on older SDK versions.or 0converts aNonevalue into0.
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
PRICESmaps 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.Totals.add()accumulates every usage field, reading optional cache fields defensively withgetattr(...) or 0.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.defaultdict(Totals)creates an emptyTotalsthe first time a model is seen, sorecord()needs no "does this key exist?" checks.by_usernests two levels, user and then model, because the same user may use different models with different prices.user_cost()adds up a user's cost across models. That is the number you need to enforce per-user limits (Lesson 179).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
- Send three requests of different lengths and print the
usage_summary()of each. - Add
claude-fable-5-1with prices(10.00, 50.00)toPRICES, and include it in the tracker's jobs. How does its cost compare? - Extend
UsageTrackerwith alimit_exceeded(user_id, limit_usd)method that returnsTruewhen a user's estimated cost exceeds a limit. - 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.
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.