Knowing what each call cost: usage figures, token counting, and a budget that stops the program
The response tells you
Every model API returns the token counts for the call it just served. Anthropic:
resp = client.messages.create(model=m, max_tokens=500, messages=msgs)
print(resp.usage.input_tokens, resp.usage.output_tokens)OpenAI:
resp = client.chat.completions.create(model=m, messages=msgs)
print(resp.usage.prompt_tokens, resp.usage.completion_tokens)With raw requests, it is r.json()["usage"]. With a stream, it arrives in the final event — Anthropic's message_delta, OpenAI's last chunk when you pass stream_options={"include_usage": True} — which is one more reason to consume a stream to its end.
These are the numbers you are billed for. Not your estimate, not the character count, these.
Turning tokens into money
Prices are per million tokens, input and output priced differently, and they change. Keep them in one place, dated:
# prices per million tokens, USD, checked 2026-09
PRICES = {
"claude-sonnet-4-5": {"in": 3.00, "out": 15.00},
"gpt-4o-mini": {"in": 0.15, "out": 0.60},
}
def cost_usd(model, usage_in, usage_out):
p = PRICES[model]
return (usage_in * p["in"] + usage_out * p["out"]) / 1_000_000A 2,000-token prompt with a 500-token reply on the first model: (2000 × 3 + 500 × 15) / 1,000,000 = $0.0135. On the second: $0.0006. The ratio between those two is the ratio between "run it over the whole dataset tonight" and "think first". Output tokens cost five times input on most models, so a verbose reply costs more than a long prompt; asking for brevity is a cost control.
A running total, and a stop
class Budget:
def __init__(self, cap_usd):
self.cap = cap_usd
self.spent = 0.0
self.calls = 0
def charge(self, model, usage_in, usage_out):
c = cost_usd(model, usage_in, usage_out)
self.spent += c
self.calls += 1
log.info("call %d: %d in, %d out, $%.4f, total $%.2f",
self.calls, usage_in, usage_out, c, self.spent)
if self.spent > self.cap:
raise RuntimeError(f"budget of ${self.cap} exceeded after {self.calls} calls")Call budget.charge(...) after every response. The raise is the important line. A loop that logs the total but never stops is a loop that emails you a bill; a loop that raises at the cap is one that emails you a traceback, which is cheaper. Set the cap below the provider-side spending limit you configured in the dashboard, so your code stops before the account does.
Log the cost per call at INFO. When a job costs more than expected, the log shows which calls were large and whether it was input or output.
Counting before you send
Sometimes you need the count before the call: to check a prompt fits the context window, to estimate a batch, to decide whether to chunk. Each provider has a way.
OpenAI's tokeniser is open, as the tiktoken package:
import tiktoken
enc = tiktoken.encoding_for_model("gpt-4o-mini")
print(len(enc.encode(text)))Anthropic offers a counting endpoint that costs nothing:
n = client.messages.count_tokens(model=m, messages=msgs).input_tokensOpen models ship their tokeniser with the weights; transformers loads it with AutoTokenizer.from_pretrained(name) and len(tok.encode(text)). Each model's tokeniser is different, so a count from one is only approximately right for another.
The rule of thumb, and where it fails
"About four characters per token" is the number everyone quotes. It is roughly true for English prose, and it fails badly in three cases your program will meet.
Non-Latin scripts. Tokenisers are trained on data dominated by English, so they have short pieces for English and long strings of Devanagari, Tamil, Arabic or Chinese get split into many small pieces. Hindi text commonly comes out at two to three times the tokens of an English translation of the same meaning. Hinglish in Latin script is closer to English. Measure your own text with the real tokeniser before estimating a batch; a job priced on the four-character rule for Indian-language input will come in far over.
Code and JSON. Punctuation, indentation and brackets each tend to be their own token. A JSON document can run at two to three characters per token.
Numbers. Long digit strings are split into small groups. A table of figures is expensive per character.
The honest rule is: the rule of thumb is for a quick sanity check on English; for anything you will be billed for, count with the tokeniser.
Where the tokens hide
The input count is more than your prompt. It includes the system prompt, every earlier turn you sent back for context, any tool definitions, and provider formatting around all of it. A chat loop that appends every turn grows its input count on every call; by turn thirty the input is thirty turns long and the cost per call has climbed with it. Module 9 handles trimming history; the reason to do it is here.
Prompt caching, where a provider offers it, changes the arithmetic: a cached prefix is billed at a fraction of the rate, and the usage figures break it out into separate fields. Read them the same way and price them at the cached rate, or your cost figure will be wrong in the pessimistic direction.
Try this now
Take twenty messages in whatever languages your users write, count each with a real tokeniser, and compute characters per token for each. Then compute what the four-character rule would have predicted. The gap for your own data is the number to remember.
The one thing to keep
Every response carries its own token counts; multiply them by the price table, keep a running total, and raise before the cap is crossed — and count Hindi or code prompts before sending, because the four-characters-per-token rule of thumb is off by two to three times for them.
Before you move on
A developer estimates the cost of a batch job by taking each prompt's length in characters, dividing by four, and multiplying by the per-token price. The prompts are customer messages, about half in Hindi written in Devanagari. The actual bill comes in at roughly two and a half times the estimate. What is the mechanism?
Pick the one you would defend. Nobody sees your answer.