
Price Before You Send: Use Claude's count_tokens API to Gate Costs and Route by Size Before Spending a Single Token
Chris Harper
3 min read
Jul 11, 2026 · 04:07 UTC
TL;DR: client.messages.count_tokens() runs a zero-cost preflight on your exact payload and returns input_tokens — wire it into a cost gate, model router, or CI budget check before any dollar is spent.
Prompt caching cuts cost after the first call; token counting cuts cost before. The count_tokens endpoint accepts the same structure as messages.create — model, system prompt, messages, and tools — and returns the input token count without executing the prompt or consuming any credits. It takes tens of milliseconds. Use it for four things: cost gates, model routing by size, CI budget enforcement, and per-task attribution.
The endpoint
import anthropic
client = anthropic.Anthropic()
count = client.messages.count_tokens(
model="claude-sonnet-4-6",
system="You are a code reviewer. Be concise.",
messages=[
{"role": "user", "content": f"Review this file:\n\n{file_contents}"}
]
)
print(f"Input tokens: {count.input_tokens}")
# → Input tokens: 3847
No API call to Claude; no tokens consumed. The count is per-model (different tokenizers produce different counts), so pass the model you intend to use.
A cost gate
PRICING = {
"claude-opus-4-8": (5.00, 25.00), # (input, output) per MTok
"claude-sonnet-4-6": (3.00, 15.00),
"claude-haiku-4-5-20251001": (1.00, 5.00),
}
def preflight(model, system, messages, output_estimate=500, budget_usd=0.10):
count = client.messages.count_tokens(
model=model, system=system, messages=messages
)
input_price, output_price = PRICING[model]
estimated = (
count.input_tokens / 1_000_000 * input_price
+ output_estimate / 1_000_000 * output_price
)
if estimated > budget_usd:
raise ValueError(
f"Preflight: ~${estimated:.4f} exceeds ${budget_usd} budget "
f"({count.input_tokens:,} input tokens). "
f"Summarize context or switch to a cheaper model."
)
return count.input_tokens
Call preflight() before messages.create(). If it raises, compress the context or re-route — no tokens wasted on a request that was going to blow the budget anyway.
Model routing by token count
Large inputs on cheap tasks are a common source of waste. Count first, then pick the right model:
def smart_model(system, messages):
count = client.messages.count_tokens(
model="claude-haiku-4-5-20251001", # counts are close enough for routing
system=system,
messages=messages
)
tokens = count.input_tokens
if tokens < 8_000:
return "claude-haiku-4-5-20251001" # fast + cheap for short tasks
elif tokens < 60_000:
return "claude-sonnet-4-6" # standard for most tasks
else:
return "claude-opus-4-8" # full context for large-codebase work
model = smart_model(system_prompt, messages)
response = client.messages.create(model=model, system=system_prompt, messages=messages)
Tools and images count too
Tool definitions add tokens (the JSON schema is billed as input). If you pass a large tool set, count them as part of the payload — they show up in input_tokens just like the messages do.
count = client.messages.count_tokens(
model="claude-sonnet-4-6",
system=system,
messages=messages,
tools=tool_definitions # include tools for an accurate count
)
One caveat
count_tokens returns an estimate — final billed usage may differ by a small margin (typically < 1%). Use zone logic: "over 20k? warn. over 100k? re-route." Don't hard-gate on exact equality.
Sources: Token counting — Claude Platform Docs · Count tokens in a Message — Claude API Reference · How to Count Tokens and Estimate LLM Costs Before You Ship — ML Journey