Los tokens de Claude se cuentan con el endpoint oficial POST /v1/messages/count_tokens, que recibe el modelo como parámetro porque cada generación tokeniza distinto. tiktoken es de OpenAI y subestima. Después de cada respuesta, el prompt real es la suma de input_tokens, cache_creation_input_tokens y cache_read_input_tokens, no el primer campo solo.
Si estimas el gasto de Claude con una calculadora de tokens genérica, el número que te sale no es tu factura. Y desde el tokenizador nuevo, la diferencia creció.
Esta guía es el método correcto: el endpoint oficial, los campos de la respuesta que de verdad importan, y cómo medir tu propio delta entre dos modelos en cinco minutos.
`tiktoken` es el tokenizador de OpenAI. Cuenta bien los tokens de los modelos de OpenAI y no cuenta los de Claude: para texto normal subestima, y para código o para idiomas que no son inglés subestima más.
No es un detalle académico. Si presupuestas con un conteo que se queda corto, el tope de gasto que configuraste salta antes de lo que esperabas y la ventana de contexto se llena antes de lo que calculaste.
La regla es simple: el conteo de tokens de Claude se pide a Claude.
Anthropic expone `POST /v1/messages/count_tokens`. Recibe la misma forma que una petición normal — modelo, mensajes, sistema, herramientas — y devuelve cuántos tokens de entrada tendría esa petición, sin ejecutarla ni cobrarte generación.
En Python:
```python
from anthropic import Anthropic
client = Anthropic()
resp = client.messages.count_tokens(
model="claude-sonnet-5",
messages=[{"role": "user", "content": open("documento.md").read()}],
)
print(resp.input_tokens)
```
El campo `model` no es decorativo. El conteo es específico del modelo: los modelos de la generación 4.7 en adelante usan un tokenizador distinto al de Sonnet 4.6 y anteriores, así que el mismo texto devuelve números distintos según lo que pongas ahí.
Desde la terminal, con el CLI oficial:
```bash
ant messages count-tokens --model claude-sonnet-5 --message '{role: user, content: "@./documento.md"}' --transform input_tokens -r
```
El `@` delante de la ruta inyecta el contenido del archivo en el campo.
Anthropic dice que el tokenizador nuevo produce aproximadamente un 30 % más de tokens para el mismo texto, y avisa que el aumento exacto depende del contenido y de la forma de tu carga de trabajo. Tu 30 % puede ser un 18 % o un 40 %.
Averígualo con tus propios textos, no con el promedio de nadie:
```python
texto = open("documento.md").read()
def contar(modelo):
return client.messages.count_tokens(
model=modelo,
messages=[{"role": "user", "content": texto}],
).input_tokens
viejo = contar("claude-sonnet-4-6")
nuevo = contar("claude-sonnet-5")
print(viejo, nuevo, f"{(nuevo / viejo - 1) * 100:.1f}% más")
```
Córrelo sobre cinco o seis archivos representativos de lo que realmente le mandas al modelo. Ese porcentaje es el tuyo, y es el que debes usar para recalcular presupuestos y topes.
Contar antes te da la estimación. Después de cada respuesta real, el objeto `usage` te da la verdad:
Aquí está la trampa que confunde a casi todo el mundo: `input_tokens` no es el tamaño de tu prompt. Es solo el pedazo que no estaba cacheado. El prompt completo es la suma de los tres campos de entrada:
```python
total = (r.usage.input_tokens
+ r.usage.cache_creation_input_tokens
+ r.usage.cache_read_input_tokens)
```
Si tu agente lleva horas corriendo y `input_tokens` marca cuatro mil, no es que el prompt sea pequeño: es que casi todo se está sirviendo desde la caché. Mira la suma, no el campo suelto.
Y al revés: si `cache_read_input_tokens` sale en cero petición tras petición con el mismo prefijo, la caché no está funcionando. Casi siempre es una marca de tiempo, un identificador aleatorio o un JSON serializado sin ordenar metido al principio del prompt.
Contar tokens no reduce la factura por sí solo: solo la hace predecible. La mayoría del ahorro real viene de la caché de prompt y de no mandar contexto que no hace falta.
Pero sin un conteo correcto no puedes saber si algo de eso está funcionando, porque estarías midiendo con una regla que no es la tuya.