Count Input Tokens
What Input Tokens Are
Input tokens are the tokens Claude reads to produce a response: everything in the request that is not generated output. They are reported after every request:
message = client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
system="You are a concise assistant.",
messages=[{"role": "user", "content": "What is a Python tuple?"}],
)
print(message.usage.input_tokens) # e.g. 24
Input tokens matter because they are:
- billed on every request, at the model's input price (Lesson 30),
- limited by the context window (1M tokens for current models), together with the output,
- rate-limited per minute by your organization's tier, and
- a source of latency, since larger inputs take longer to process.
In many applications, input tokens are the larger part of the bill, because conversation history, documents, and instructions are resent with every request.
What Counts as Input
| Part of the request | Counted as input? | Notes |
|---|---|---|
system prompt | Yes | Sent with every request |
All messages (user and assistant turns) | Yes | The whole history, every time |
Tool definitions (tools) | Yes | Names, descriptions, and schemas |
| Images and PDFs | Yes | Often many tokens each |
| Tool results | Yes | They are content in user messages |
| Structured output schemas | Yes | Part of the request's instructions |
max_tokens, model, and other settings | No | Configuration, not content |
Token counts can also include a small number of tokens that the API adds automatically, for example to format tools or system instructions. According to the token counting documentation, you are not billed for tokens added by Anthropic for system optimizations. Billing reflects your content.
Reading Input Usage Correctly
With prompt caching, the input is split across three fields (Lesson 30):
usage = message.usage
total_input = (
usage.input_tokens
+ (usage.cache_creation_input_tokens or 0)
+ (usage.cache_read_input_tokens or 0)
)
input_tokenscounts only the input processed without caching.total_inputis the full size of what Claude read.
If you look only at input_tokens in an application that uses caching, the input looks much smaller than it really is. That is correct for cost at the normal input price, but wrong for context size.
Input Grows in Conversations
Every turn resends the entire history (Lesson 24), so input tokens grow turn by turn:
Turn 1: system + user1 → 120 input tokens
Turn 2: system + user1 + reply1 + user2 → 520
Turn 3: system + user1 + reply1 + user2 + reply2 + user3 → 1,050
Tracking input_tokens per turn shows you when to trim, summarize, or cache, before the conversation becomes slow and expensive.
The Tokenizer Matters
A tokenizer is the component that splits text into tokens. Different model generations can use different tokenizers. The tokenizer introduced with recent Claude models produces noticeably more tokens for the same text than older models did.
Consequences:
- Token estimates, limits, and budgets measured on an older model are too low for current models.
- Cost comparisons between model generations must use each model's own token counts.
- When you change models, re-measure with the new model, for example with
count_tokens()(Lesson 46).
The same text also produces different token counts depending on language and content. Code, numbers, and many non-English scripts typically use more tokens per word than plain English prose.
Finding Where Input Tokens Come From
When requests are larger than expected, find out which part of the request is responsible. The token counting endpoint (Lesson 46) counts a request's input without generating a response, so you can measure a request piece by piece:
- Count the request with only a minimal message.
- Add the system prompt and count again.
- Add the tools and count again.
- Add the history and the new message.
The difference between successive counts is the cost of each added part.
Practical Example: An Input Token Breakdown
This script measures how many input tokens each part of a realistic request contributes: system prompt, tools, conversation history, and the new message. It uses count_tokens(), which is free to use and generates nothing.
"""input_breakdown.py: measure which parts of a request use the most input tokens."""
from anthropic import Anthropic
client = Anthropic()
MODEL = "claude-opus-5-5"
SYSTEM = (
"You are a support assistant for an online Python course platform. "
"Answer in under 100 words. If you are unsure, say so and suggest emailing support@example.com. "
) * 5 # Repeated to simulate a longer, realistic system prompt
TOOLS = [
{
"name": "get_course_progress",
"description": "Get a student's progress in a course, as a percentage.",
"input_schema": {
"type": "object",
"properties": {
"student_id": {"type": "string"},
"course_id": {"type": "string"},
},
"required": ["student_id", "course_id"],
},
},
]
HISTORY = [
{"role": "user", "content": "Hi! I'm working through the Django course."},
{"role": "assistant", "content": "Great! Let me know if you have questions about any lesson."},
{"role": "user", "content": "What's the difference between a model and a view?"},
{"role": "assistant", "content": "A model defines your data structure and database table; "
"a view handles a request and returns a response."},
]
NEW_MESSAGE = {"role": "user", "content": "How far am I in the Django course?"}
MINIMAL = [{"role": "user", "content": "Hi"}]
def count(**request) -> int:
return client.messages.count_tokens(model=MODEL, **request).input_tokens
def breakdown() -> None:
steps = [
("minimal message", {"messages": MINIMAL}),
("+ system prompt", {"system": SYSTEM, "messages": MINIMAL}),
("+ tools", {"system": SYSTEM, "tools": TOOLS, "messages": MINIMAL}),
("+ history and new message", {"system": SYSTEM, "tools": TOOLS,
"messages": HISTORY + [NEW_MESSAGE]}),
]
previous = 0
print(f"{'Request':<28}{'Total':>8}{'Added':>8}")
for label, request in steps:
total = count(**request)
print(f"{label:<28}{total:>8}{total - previous:>+8}")
previous = total
if __name__ == "__main__":
breakdown()
Example output (exact numbers vary by model and content):
Request Total Added
minimal message 8 +8
+ system prompt 186 +178
+ tools 612 +426
+ history and new message 701 +89
How It Works
count()callsclient.messages.count_tokens()with the model and the request parts, and returnsinput_tokens. No response is generated and no output tokens are used.- Each step adds one part to the previous request. The "Added" column, the difference from the previous total, is the cost of that part.
MINIMALis needed because a request must contain at least one message. Its few tokens form the baseline.- The
:>8and:>+8format specifiers right-align the numbers in 8-character columns. The+shows a sign on the differences.
In this example, the tools add more input than the conversation history. That is common: detailed tool schemas are sent with every request. Insights like this tell you where optimization pays off: shorter tool descriptions, fewer tools per request, or caching the stable system-plus-tools prefix (Lessons 121–130).
Note: Counts from
count_tokens()are estimates. The actual input of a real request can differ by a small amount. They are accurate enough for breakdowns, budgets, and comparisons.
Reducing Input Tokens
| Source | Technique |
|---|---|
| Long system prompts | Remove repetition; keep only instructions that change behavior; cache the stable part |
| Tool definitions | Send only the tools relevant to the request; keep descriptions precise but concise |
| Conversation history | Trim old turns; summarize long histories (Lessons 24, 27, 64) |
| Documents | Send relevant excerpts instead of whole documents (Lesson 33) |
| Repeated stable content | Prompt caching makes repeated prefixes much cheaper (Lessons 121–130) |
| Images | Resize images to the resolution the task actually needs (Lesson 111) |
Measure before and after each change. Some reductions save very little, while others, such as removing unused tools, save a lot.
Common Mistakes
Ignoring Cache Fields When Measuring Context Size
What happens: input_tokens looks small, but the request is actually close to the context limit because most input was read from the cache.
Fix: add cache_creation_input_tokens and cache_read_input_tokens when you measure the full input size.
Forgetting That Tools Count
What happens: large tool sets silently add hundreds or thousands of tokens to every request.
Fix: measure tool overhead, and send only the tools each request needs.
Reusing Token Estimates Across Models
What happens: budgets based on an older model's tokenizer are exceeded on current models.
Fix: re-measure with the model you actually use.
Estimating Tokens from Character or Word Counts
What happens: estimates are off, especially for code and non-English text.
Fix: use count_tokens() for accurate numbers, and use rough ratios only for quick sketches.
Not Watching History Growth
What happens: long chats become slow and expensive without anyone noticing.
Fix: log input tokens per turn, and act when they cross a threshold.
Best Practices
- Log input tokens for every request, including the cache fields.
- Know what your requests contain: measure the system prompt, tools, history, and documents separately.
- Optimize the largest contributors first.
- Re-measure after changing models or prompts.
- Use prompt caching for large, stable prefixes.
- Treat
count_tokens()results as close estimates, which are suitable for budgets and comparisons.
Exercise
- Run
input_breakdown.py. Which part adds the most tokens? - Add a second tool with a long description, and measure how much it adds.
- Count the tokens of the same 100-word paragraph in English and in another language you know. Compare the results.
- Write a function
context_usage(message, context_window=1_000_000)that returns the total input (including cache fields) as a percentage of the context window.
Step 3: many non-English languages, especially those with non-Latin scripts, produce more tokens for the same meaning than English. That affects both cost and how much text fits into the context window.
Step 4:
def context_usage(message, context_window: int = 1_000_000) -> float:
usage = message.usage
total_input = (
usage.input_tokens
+ (usage.cache_creation_input_tokens or 0)
+ (usage.cache_read_input_tokens or 0)
)
return 100 * total_input / context_window
For example, a total input of 50,000 tokens returns 5.0, meaning 5% of the context window. For other models, read the window size from the Models API (max_input_tokens, Lesson 12) instead of hardcoding it.