Token Economics Basics
9 examples to get you started with token economics - 6 basic and 3 intermediate.
Prerequisites
- Install the SDK:
pip install anthropic - Set your API key in the environment:
export ANTHROPIC_API_KEY=sk-ant-... - Every example below uses
anthropic.Anthropic(), which reads that environment variable automatically.
Basic Examples
1. Count Tokens Before Sending a Request
Get an exact token count for a request without paying for a completion.
import anthropic
client = anthropic.Anthropic()
count = client.messages.count_tokens(
model="claude-sonnet-5",
messages=[{"role": "user", "content": "Summarize the Q3 report in three bullets."}],
)
print(count.input_tokens)count_tokensmirrors themessages.createsignature, so you pass the samesystem,tools, andmessagesyou'd send for real.- It returns only
input_tokens, since output length isn't known until generation happens. - This call itself is free, it does not run the model or generate a completion.
- Use it as a dry run before any request whose size is unpredictable, like user-supplied documents.
Related: How Claude Token Pricing Actually Works - why input and output are priced differently.
2. Estimate Cost from a Token Count
Turn a token count into a dollar estimate using a model's published rate.
SONNET_INPUT_PER_MTOK = 2.00 # intro pricing, through 2026-08-31
count = client.messages.count_tokens(
model="claude-sonnet-5",
messages=[{"role": "user", "content": "Summarize the Q3 report in three bullets."}],
)
estimated_input_cost = (count.input_tokens / 1_000_000) * SONNET_INPUT_PER_MTOK
print(f"${estimated_input_cost:.6f}")- Dividing by
1_000_000converts a raw token count into millions of tokens, matching how per-MTok pricing is quoted. - This only estimates the input side, output cost is unknown until the model actually responds.
- Keep the rate constant in one place, not scattered inline, so a pricing update is a one-line change.
- For a rough upper bound, multiply
max_tokensby the output rate and add it to this estimate.
3. Read Actual Usage from a Completed Response
Get exact, billed token counts after a real call.
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=300,
messages=[{"role": "user", "content": "Summarize the Q3 report in three bullets."}],
)
print(response.usage.input_tokens, response.usage.output_tokens)response.usageis the source of truth, it reflects what you're actually billed for.count_tokensbefore the call andusageafter the call should report the sameinput_tokensfor an identical request.output_tokensis only available here, never from a pre-call estimate.- Log this on every call if you're building any kind of cost dashboard.
4. Compute the Real Dollar Cost of a Response
Combine input and output usage with per-model rates for an exact cost.
RATES = {
"claude-sonnet-5": {"input": 2.00, "output": 10.00},
"claude-haiku-4-5": {"input": 1.00, "output": 5.00},
}
def cost_of(response, model: str) -> float:
rate = RATES[model]
usage = response.usage
return (
(usage.input_tokens / 1_000_000) * rate["input"]
+ (usage.output_tokens / 1_000_000) * rate["output"]
)
print(f"${cost_of(response, 'claude-sonnet-5'):.6f}")- Keeping rates in a dict keyed by model name makes it trivial to add Opus or Fable later.
- This function is the building block for any per-request cost logging you add downstream.
- It ignores cache tokens for now, see example 6 for the caching-aware version.
- Round for display, but store the unrounded float if you're aggregating costs over many calls.
Related: Building a Pre-Request Cost Calculator with the Token Counting API - wiring this into a pipeline gate.
5. Count Tokens for a Request That Includes Tools
Confirm that tool definitions count toward the input total.
tools = [
{
"name": "get_weather",
"description": "Get the current weather for a city.",
"input_schema": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
}
]
count = client.messages.count_tokens(
model="claude-sonnet-5",
tools=tools,
messages=[{"role": "user", "content": "What's the weather in Austin?"}],
)
print(count.input_tokens)- Tool schemas are part of the input prefix and are billed like any other input tokens.
- A large tool catalog with many tools and long descriptions can dominate a small user message's token count.
- Comparing this count to example 1's shows exactly how many tokens the tool definitions add.
- This is the number that matters when deciding whether a tool catalog is worth caching.
6. Count Tokens for a Multi-Turn Conversation
See how token count grows as conversation history accumulates.
conversation = [
{"role": "user", "content": "What's our refund policy?"},
{"role": "assistant", "content": "Refunds are accepted within 30 days of purchase."},
{"role": "user", "content": "Does that apply to opened items?"},
]
count = client.messages.count_tokens(
model="claude-sonnet-5",
messages=conversation,
)
print(count.input_tokens)- Every prior turn, both user and assistant, is resent as input on each new call, that's how the model has memory.
- Token count for a conversation grows roughly linearly with turn count, which is why long-running chats get progressively more expensive per turn.
- This is the exact mechanism prompt caching is designed to offset for the stable parts of a conversation.
- Checking this count periodically in a long chat helps catch runaway context growth before it becomes a cost surprise.
Intermediate Examples
7. Build a Pre-Send Cost Gate
Refuse to send a request if its estimated cost exceeds a budget.
def send_if_affordable(client, max_cost_usd: float, **kwargs):
count = client.messages.count_tokens(
model=kwargs["model"],
messages=kwargs["messages"],
system=kwargs.get("system"),
tools=kwargs.get("tools"),
)
rate = RATES[kwargs["model"]]
input_cost = (count.input_tokens / 1_000_000) * rate["input"]
# Worst case: assume the full max_tokens budget is used as output.
worst_case_output_cost = (kwargs.get("max_tokens", 0) / 1_000_000) * rate["output"]
if input_cost + worst_case_output_cost > max_cost_usd:
raise ValueError(
f"Estimated cost ${input_cost + worst_case_output_cost:.4f} exceeds budget ${max_cost_usd}"
)
return client.messages.create(**kwargs)
response = send_if_affordable(
client,
max_cost_usd=0.05,
model="claude-sonnet-5",
max_tokens=300,
messages=[{"role": "user", "content": "Summarize the Q3 report in three bullets."}],
)- The worst-case output estimate uses
max_tokensas an upper bound, since real output length is unknown pre-call. - This pattern is the seed of a per-tenant or per-feature budget guard in a multi-user application.
- Raising before the call means you never pay for a request you'd have rejected anyway.
- Tune the worst-case assumption if your typical completions run well under
max_tokens, a flat cap can be too conservative.
8. Track Running Spend Across a Batch of Calls
Accumulate cost across many requests in a loop.
def process_documents(client, documents: list[str], model: str) -> float:
total_cost = 0.0
for doc in documents:
response = client.messages.create(
model=model,
max_tokens=200,
messages=[{"role": "user", "content": f"Extract the key entities from:\n{doc}"}],
)
total_cost += cost_of(response, model)
return total_cost
total = process_documents(client, ["doc one text...", "doc two text..."], "claude-haiku-4-5")
print(f"Total run cost: ${total:.4f}")- Accumulating
cost_of(...)per call turns a batch job into a job with a known, auditable total cost. - Running this against Haiku first for a cheap sanity check before switching to a stronger model is a common pattern.
- This same loop is what you'd instrument with logging or a metrics emitter in production.
- Compare the per-document average against your budget per document, not just the batch total, to catch cost outliers.
Related: Model Tiering: Routing Simple Tasks to Haiku, Hard Tasks to Opus - choosing the model this loop calls.
9. Compare Estimated vs Actual Cost to Catch Drift
Verify your pre-call estimate matches what you were actually billed.
count = client.messages.count_tokens(
model="claude-sonnet-5",
messages=[{"role": "user", "content": "Summarize the Q3 report in three bullets."}],
)
estimated_input_tokens = count.input_tokens
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=300,
messages=[{"role": "user", "content": "Summarize the Q3 report in three bullets."}],
)
actual_input_tokens = response.usage.input_tokens
assert estimated_input_tokens == actual_input_tokens, "Estimate and actual usage diverged"- For an identical request,
count_tokensinput andusage.input_tokensshould always match exactly. - A mismatch usually means the request changed between the estimate and the real call, not a pricing bug.
- Wiring this assertion into a test suite catches accidental prompt drift between a cost estimator and the live code path.
- This check has no bearing on output tokens, which only exist after generation.
Stack versions: Written against the Claude model lineup current as of ~June 2026 - Claude Fable 5, Claude Opus 4.8, Claude Sonnet 5 (the default), and Claude Haiku 4.5 - and the official
anthropicPython SDK (latest 0.x release). Model names, pricing, and SDK versions move quickly - verify current specifics at platform.claude.com/docs before relying on them.