# How to count tokens before you call the API: OpenAI, Claude, Gemini

URL: https://infro.io/blog/count-tokens-before-api-call
Author: INFRO Editorial (https://infro.io/blog/editorial)
Publisher: INFRO
Published: 2026-10-20
Last modified: 2026-10-20
Last reviewed: 2026-10-20 (next review due 2026-11-19)
Topic: Cost and FinOps
Format: Guide

> To count tokens before a request, use the provider's counting endpoint with the exact payload you will send: OpenAI's POST /v1/responses/input_tokens, Anthropic's POST /v1/messages/count_tokens (free, rate-limited separately) or Gemini's countTokens. Local tokenizers such as tiktoken count plain text well but miss formatting tokens, tools, images and files. No endpoint can count output, so a cost estimate is the input count times the input rate plus max_tokens times the output rate as the worst case.

Key takeaways:
- Count with the provider's own endpoint and the exact payload you will send; local tokenizers miss formatting, tool, image and file tokens.
- OpenAI: POST /v1/responses/input_tokens. Anthropic: POST /v1/messages/count_tokens, free with its own rate limit. Gemini: countTokens.
- Counts are per model: Claude 4.7 and later produce about 30% more tokens than earlier Claude models for the same text.
- No endpoint counts output. Bound it with max_tokens and price the worst case.
- Use local counting for rough sizing in hot paths and endpoint counting where the number decides something: a budget, a route or a context trim.

**How do you count tokens before calling an LLM API?** Send the exact request payload to the provider's counting endpoint: `POST /v1/responses/input_tokens` on OpenAI, `POST /v1/messages/count_tokens` on Anthropic, or `countTokens` on Gemini. Each returns the input token count for that model. A local tokenizer such as tiktoken is faster and free, but it only counts plain text correctly; tools, images, files and the formatting around messages are where local counts go wrong.

This guide covers the three providers' methods, what each counts, and how to turn a count into a cost bound you can act on.

## Why count before the call

Four decisions need a token count before the provider sees the request:

1. **Cost.** Input count times input rate, plus the output limit times the output rate, is the most a request can cost. That is what a budget check needs.
2. **Context fit.** A prompt that will not fit the model's window should be trimmed or summarised before it is sent, not after the 400.
3. **Pricing thresholds.** Some rates change with prompt length: OpenAI above 272K input tokens, Claude Haiku 5.5 above 100,000, Gemini 3.1 Pro above 200K. A request just over the line is re-priced as a whole.
4. **Routing.** Small prompts can go to a small model and very large ones to a model with the window for them.

## The three providers' methods

| Provider | Method | Counts | Cost to call | Notes |
| --- | --- | --- | --- | --- |
| OpenAI | `POST /v1/responses/input_tokens` | Text, messages, images, files (PDF), tools, formatting | Not stated | Same payload as a Responses request |
| Anthropic | `POST /v1/messages/count_tokens` | System prompt, messages, tools, base64 images and PDFs, thinking | Free; 5,000–20,000 RPM by tier | Refuses server tools and URL-sourced media |
| Google | `models.count_tokens` / `countTokens` | Contents, system instructions, tool definitions | Not stated | Returns input total only |
| Local | tiktoken and similar | Plain text | Free | No images, files, tools or message formatting |

Table: Token counting methods as each provider documents them, 2026-10-20.

### OpenAI

OpenAI's counting endpoint accepts the same input as `responses.create` and returns `{"object": "response.input_tokens", "input_tokens": N}`. Its guide names exactly where local counting fails: images and files ("estimates like characters / 4 are inaccurate"), tool and schema definitions, and model-specific behaviour. It also notes that the count "includes formatting tokens used to represent request structure, such as message roles and boundaries"[1].

```python
from openai import OpenAI

client = OpenAI()
count = client.responses.input_tokens.count(
    model="gpt-6-sol",
    instructions=open("system_prompt.txt").read(),
    input="Summarise the attached ticket thread.",
    tools=[{"type": "function", "name": "lookup_order", "parameters": {"type": "object", "properties": {}}}],
)
print(count.input_tokens)
```

For plain text in a hot path, tiktoken is OpenAI's open-source BPE tokenizer: `tiktoken.encoding_for_model("gpt-4o")` returns that model's encoding, and `o200k_base` and `cl100k_base` can be loaded by name[2]. Treat a local count as a lower bound for a chat request, because it does not see roles, tools or attachments.

### Anthropic

Anthropic's endpoint takes the same body as a message request — system prompt, messages, tools, images and PDFs as base64, thinking settings — and returns `input_tokens`. It is free, rate-limited per minute by usage tier, and its limits are independent of message creation[3]. Three behaviours to know:

- **It is an estimate.** Anthropic says the actual input tokens "might differ by a small amount", and that tokens it adds for system optimisations are not billed[3].
- **It refuses some inputs.** Server tools (web search, web fetch, code execution), the MCP connector and images or documents given by URL or file id return `invalid_request_error`. Count those requests from the `usage` of the real response instead.
- **It counts per model.** Claude 4.7 and later, including the Fable family, use a tokenizer that produces roughly 30% more tokens for the same text. Anthropic's advice is to recount against the model you plan to use rather than reuse old counts[3].

```bash
curl https://api.anthropic.com/v1/messages/count_tokens \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-sonnet-5-5",
    "system": "You are a support assistant.",
    "messages": [{"role": "user", "content": "Where is order 4471?"}]
  }'
# {"input_tokens": ...}
```

On a conversation with extended thinking, previous turns' thinking blocks count as input on models that keep them, and the current turn's thinking counts too[3].

### Google Gemini

Gemini's SDKs expose `client.models.count_tokens` (Python) and `countTokens` (JavaScript), which return the input total; Google recommends calling it before sending large inputs[4]. Its guide also gives fixed costs for media that are useful for estimates without a call:

- Text: about 4 characters per token; 100 tokens is about 60 to 80 English words.
- Images: 258 tokens up to 384 pixels on each side; larger images are tiled at 768 by 768, 258 tokens per tile.
- Audio: 32 tokens per second.
- System instructions and tool definitions count as input[4].

```python
from google import genai

client = genai.Client()
result = client.models.count_tokens(
    model="gemini-3.8-flash",
    contents="Summarise the attached ticket thread.",
)
print(result.total_tokens)
```

## What no endpoint can count

**Output.** Generated tokens, including reasoning or thinking tokens, exist only after generation. OpenAI bills reasoning as output and reports it afterwards; Gemini's output price includes thinking tokens. The only pre-call control is the output limit: `max_output_tokens` or `max_completion_tokens` on OpenAI, `max_tokens` on Anthropic, `maxOutputTokens` on Gemini. Price the limit, not the expected length.

That gives a simple upper bound:

```text
max_cost = input_tokens × input_rate + max_output_tokens × output_rate
# input_tokens from the counting endpoint, rates per token for the target model
# a cached prefix lowers the real cost; it never raises the bound above this
```

> **Example: Bounding a Claude Sonnet 5.5 request**
> `count_tokens` returns 12,400 input tokens and the request sets `max_tokens` to 2,000. At $2 input and $10 output per million: 0.0124 × $2 + 0.002 × $10 = $0.0448 at most. If the prefix is cached the real figure is lower; the bound still holds.

## Counting across providers

A count is only meaningful for the model it was taken on. The same prompt can produce materially different counts on OpenAI, Anthropic and Google, and even between two generations of one vendor's models. Three consequences:

- **Do not compare rates without counts.** A model with a lower rate per token can cost more per request if its tokenizer produces more tokens for your text. Compare cost per request on your own prompts.
- **Re-count on migration.** Moving a workload between providers, or across Claude's 4.6-to-4.7 tokenizer change, changes the count before it changes the rate.
- **Keep counts per target.** A gateway that may fail over from one provider to another should bound cost with the most expensive target in the chain.

## Local or endpoint: which to use where

| Situation | Use |
| --- | --- |
| Hot path, plain text, rough sizing | A local tokenizer, with a safety margin |
| A budget or spend-cap decision | The provider's endpoint, or the real response's usage afterwards |
| Requests with images, PDFs or many tools | The provider's endpoint |
| Requests near a pricing or context threshold | The provider's endpoint |
| After the call, for accounting | The `usage` object of the response — never a re-count |

Endpoint calls add latency, so where counting sits on the request path, cache counts for stable prefixes (system prompts, tool lists) and count only the changing part.

## Counting with INFRO

INFRO's gateway needs a bound before every request it serves, because budgets are checked before the provider is called: it reserves a worst-case estimate from the prompt size and the request's output limit, admits the request only if that fits the room left, and records the provider-reported usage afterwards. You can ask for the same estimate yourself with `POST /v1/estimates` without sending the request. For Claude Code and other clients on INFRO's `/v1/messages`, `POST /v1/messages/count_tokens` asks the first Anthropic connection that serves the model, is free, and leaves no request row; with no such connection the answer is an estimate and the `X-INFRO-Token-Count: estimated` header says so.

## Summary

Count where the number decides something, with the exact payload, on the exact model. Use local tokenizers only as a fast lower bound for plain text, bound output with an explicit limit, and treat the usage in each response as the figure of record.

## Questions

### How do I count tokens for GPT models?

For plain text, tiktoken gives a fast local count: tiktoken.encoding_for_model returns the encoding for a known model. For a full request with tools, images or files, call OpenAI's POST /v1/responses/input_tokens with the same payload you would send to the Responses API; it includes formatting tokens a local count misses.

### Is Anthropic's token counting API free?

Yes. Anthropic says token counting is free to use, with requests-per-minute limits by usage tier (5,000 to 20,000) that are separate from message creation. The count is an estimate and can differ by a small amount from the tokens billed.

### How many characters is a token?

Roughly four characters of English text, which Google gives as its rule of thumb for Gemini. It varies by language, by code versus prose, and by model: Anthropic says Claude 4.7 and later models produce about 30% more tokens for the same text than earlier Claude models.

### Can I count output tokens before the request?

No. Output, including reasoning or thinking tokens, is only known after generation. Set max_tokens or max_output_tokens and treat that limit as the worst case when you estimate cost.

## Sources

1. Counting tokens — OpenAI. https://developers.openai.com/api/docs/guides/token-counting (accessed 2026-10-20)
2. openai/tiktoken — GitHub. https://github.com/openai/tiktoken (accessed 2026-10-20)
3. Token counting — Anthropic. https://platform.claude.com/docs/en/build-with-claude/token-counting (accessed 2026-10-20)
4. Understand and count tokens — Google. https://ai.google.dev/gemini-api/docs/tokens (accessed 2026-10-20)
