Module 01 · Lesson 4 of 13Free
Tokens: how models read text and charge for it
- Level
- Beginner
- Topic
- AI engineering fundamentals
- Reading time
- 8 min
When you finish this page you can
- Explain what a token is and why a message has more tokens than words.
- Predict which texts take more tokens for the same meaning, such as other languages, code and numbers.
- Name what a model API counts in tokens, thinking included, and what each count controls.
- Count a request’s tokens before sending it, with the token counting endpoint in Python.
Builds on 1 lesson
Start with lesson 315 minVideo lesson · coming soon
A 38-word message is not 38 tokens
Deskora is a help desk app. A shop’s support team uses it to answer customer emails from one shared inbox. Each customer problem becomes a ticket: one customer’s conversation with the support team. Tomás, a Deskora engineer, is building a short summary for long tickets.
Here is the first message of one ticket, from a customer named Meera:
Hi, I ordered a pressure cooker on 12 March (order 58214) and it has not arrived yet. The tracking page says it is in transit, but nothing has changed for six days. Can you check where it is?
It has 38 words. But the model API bills tokens, not words. Tomás’s estimate of about $38 a day for summaries, in Calling a model API, rests on a guess: about 4,000 tokens per thread.
Meera’s message has 38 words. Will Claude count about 38 tokens, fewer, or more?Show answer
This lesson answers one question: what exactly is Tomás paying for, and how can he count it before he sends a ticket? The token counts here are illustrative; the code near the end gives real ones.
A token is a common piece of text
Before the model sees Meera’s message, a tokenizer cuts it into tokens: pieces of text from a fixed list called the vocabulary. Each token becomes a number, and the model reads and writes those numbers, as Next-token prediction shows.
Diagram not drawn yet: deskora-message-token-split.svg
Meera’s 38-word message drawn as a row of 47 small boxes, one per token. Most words, each with its leading space, are one box: Hi, I, ordered, pressure, tracking. The word cooker is two boxes, cook and er. The order number 58214 is two boxes, 582 and 14. Each comma, full stop, bracket and the question mark is its own box. A note says the split is illustrative because Claude’s tokenizer is not public.
Why pieces, and not words or letters? A vocabulary of whole words would need every name, typo and order number, and would still miss new ones. Single characters make every message several times longer, and the model’s work grows with length. Tokens sit between: frequent words get one token, and everything else is built from several.
Anthropic doesn’t publish the tokenizer for current Claude models, so the real boundaries may differ. Only the API gives you a real count.
The same meaning, very different counts
The same shop also gets tickets in Hindi. Here is Meera’s message, translated:
नमस्ते, मैंने 12 मार्च को एक प्रेशर कुकर ऑर्डर किया था (ऑर्डर 58214), लेकिन वह अभी तक नहीं पहुँचा है। ट्रैकिंग पेज पर लिखा है कि वह रास्ते में है, पर छह दिनों से कुछ नहीं बदला। क्या आप पता कर सकते हैं कि वह कहाँ है?
The Hindi version has only 24 more characters than the English one. Will it take about the same number of tokens?Show answer
A common rule says “a token is about three-quarters of a word”. It breaks for other languages, code and numbers: the count depends on the tokenizer, the language and the type of content 2.
| Sample | Characters | Tokens (illustrative) | Tokens per 100 characters |
|---|---|---|---|
| Meera’s message, English | 191 | 47 | 25 |
| The same message, Hindi | 215 | 130 | 60 |
| Two lines of Python | 112 | 30 | 27 |
| Order line with IDs and dates | 102 | 46 | 45 |
This gap is not special to Claude. One study found the same text in different languages up to 15 times apart in tokens 3. The model matters too: Claude models from 4.7 on use a newer tokenizer that produces about 30% more tokens for the same text 2.
Price, context and max_tokens all count tokens
| What | Counted in tokens |
|---|---|
| Price | Input tokens at the input price, plus output tokens at the output price |
| Output | Thinking plus the visible text, both billed as output 4 |
max_tokens |
The ceiling on output tokens, thinking included |
| Context window | All the tokens one call can hold |
Here is lesson 03’s cost formula applied to a Hindi ticket. Claude Haiku 5.5 costs $0.10 per million input tokens and $0.50 per million output tokens, as of October 2026 2.
English thread (from lesson 03)
4,150 × $0.10 ÷ 1,000,000 + 380 × $0.50 ÷ 1,000,000 = $0.000605
4,150 input tokens. Assumed: 300 thinking tokens and an 80-token summary.
The same thread in Hindi
4,000 tokens × 2.8 = 11,200 tokens; + 150 system = 11,350 input tokens
This lesson’s illustrative ratio. The summary stays in English, so output is unchanged.
Cost of one Hindi summary
11,350 × $0.10 ÷ 1,000,000 + 380 × $0.50 ÷ 1,000,000 = $0.001135 + $0.00019 = $0.001325
About 2.2 times the English summary.
If Deskora charges every shop the same, a shop with mostly Hindi tickets costs about twice as much to serve; Charging for AI features deals with that. The Hindi thread also fills nearly three times as much of the context window, covered in Context windows. One ratio can’t serve every ticket, so Tomás needs a real count.
Count tokens before you send
How should Tomás learn a ticket’s token count before he sends it?
Estimate from characters or words with a fixed ratio.
Free and instant, but wrong by a different amount for each language and type of text. A ratio taken from Meera’s English message predicts about 53 tokens for the Hindi one, less than half of 130.
Run another provider’s tokenizer library on his server.
Exact for the models it was made for, and wrong for Claude. A library such as tiktoken counts with OpenAI’s tokenizers, which split text differently.
Ask the API, with the token counting endpoint.
A count for the exact model, system prompt and messages, at no charge. It costs one extra request, which has its own rate limit, and the count is an estimate that can differ from the billed number by a small amount.
Send the call and read usage afterwards.
Exact, because every response reports usage. But it arrives after Deskora has paid, so it can’t change the request.
Why this choice
Only the API uses Claude’s own tokenizer before money is spent. Counting is free, with its own rate limit: 5,000 requests a minute on the lowest usage tier, as of October 2026 1. Still log usage after every call: that is the billed number.
When the choice changes. Count first only when a decision depends on the count. On Claude Haiku 5.5, a prompt over 100,000 tokens pays five times the price per token 2, so a count near that line matters. For millions of small, similar calls, a ratio per language from your own logged usage is often close enough.
Diagram not drawn yet: count-then-send.svg
Left to right. 1: the Deskora app server sends the system prompt and thread to POST /v1/messages/count_tokens, labelled free. 2: a dashed reply returns input_tokens 11,350. 3: a decision box asks whether the count is over 100,000 tokens; the no branch continues. 4: the app server sends the same request to POST /v1/messages, labelled billed. 5: a dashed reply returns the summary and usage, with input_tokens about 11,350, labelled the billed count, to log.
The endpoint, POST /v1/messages/count_tokens, takes the same model, system and messages as a normal call and returns one field, input_tokens 1. It counts input only: output, thinking included, is unknown until the model writes it.
import anthropic
client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY from the environment
MODEL = "claude-haiku-5-5"
INPUT_PRICE = 0.10 # US dollars per million input tokens, as of October 2026
LONG_PROMPT = 100_000 # above this, Haiku 5.5 charges 5 times per token
SYSTEM = (
"You summarize a support ticket for the agent who picks it up. "
"Write exactly three lines: the problem, what has been tried, "
"and what the customer is waiting for."
)
def count_summary_input(thread: str) -> int:
result = client.messages.count_tokens(
model=MODEL,
system=SYSTEM,
messages=[{"role": "user", "content": thread}],
)
return result.input_tokens
if __name__ == "__main__":
with open("ticket.txt", encoding="utf-8") as f:
thread = f.read()
tokens = count_summary_input(thread)
cost = tokens * INPUT_PRICE / 1_000_000
print(f"{len(thread)} characters, {tokens} input tokens, ${cost:.6f} of input")
if tokens > LONG_PROMPT:
print("Over 100,000 tokens: this call would pay the long-prompt price.")Running it costs nothing, because counting is free; you need an . Put Meera’s message in ticket.txt in English, then in Hindi, and compare.
Tokenizers change between model generations, so a count from one model can be wrong for another. Anthropic advises recounting with the model you plan to use 1.
Tomás can now count a ticket’s tokens. The model they go to sets the price of each one, which is the subject of Choosing a model.
Check your understanding
A shop’s customers switch from writing in English to writing in Hindi. The number of tickets stays the same. What happens to the cost of their summaries, and why?Show answer
A summary takes 80 tokens in English. Tomás asks for it in Hindi, with max_tokens set to 400 and about 300 thinking tokens. What can go wrong?Show answer
Tomás counts a ticket with count_tokens, then sends it. usage.input_tokens is 3 tokens higher than the count. Is that a bug?Show answer
Summary
- Tomás pays for tokens, pieces of text often smaller than words, and counts them before sending with the free
client.messages.count_tokens. - Common words are one token; rare words, numbers and other scripts take several.
- The same meaning can take very different counts: Hindi nearly three times English, in this lesson’s illustrative numbers.
- Price, the context window and
max_tokensare all counted in tokens, thinking included. - Count with the model you will use, not a fixed ratio or another provider’s tokenizer, and log
usagefor the billed number.