Text
Count tokens
Measure how large a request is before spending anything on it.
POST
/v1/messages/count_tokensTakes the same body as Messages and returns only the input token count. Useful for staying inside a context window, predicting cost, and deciding when to trim conversation history.
curl https://api.xkiro.com/v1/messages/count_tokens \
-H "x-api-key: $XKIRO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-opus-5",
"system": "You are a concise assistant.",
"messages": [{ "role": "user", "content": "Hello there" }]
}'200 OK
{ "input_tokens": 19 }Request body#
| Field | Type | Description |
|---|---|---|
modelrequired | string | Full model ID — tokenisation differs between model families. |
messagesrequired | array | The conversation to measure. |
system | string | array | System prompt. Counted as part of the input. |
tools | array | Tool definitions. These count too, on every turn of a tool-calling loop. |
Tool definitions are not free
They are re-sent with every request in a tool-calling loop, so a large schema is a recurring cost. Counting a request with and without tools shows exactly what they add.
A practical use#
Trim history until the request fits, instead of discovering the limit through a failed call:
async function fitToWindow(messages: unknown[], budget = 150_000) {
const trimmed = [...messages];
// Drop from the oldest end until it fits. Keeping the newest turns matters
// more than keeping the first ones — the model needs recent context.
while (trimmed.length > 2) {
const { input_tokens } = await fetch(
"https://api.xkiro.com/v1/messages/count_tokens",
{
method: "POST",
headers: {
"x-api-key": process.env.XKIRO_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "anthropic/claude-opus-5",
messages: trimmed,
}),
},
).then((r) => r.json());
if (input_tokens <= budget) break;
trimmed.shift();
}
return trimmed;
}Leave headroom for the answer: the context window covers input and output, so a budget equal to the full window leaves the model no room to reply.
