← Blog

Tracking Claude API Token Usage Per Conversation

2026-10-18 · 5 min read · SubToAPI Team

If you're building anything with the Claude API that involves multi-turn conversations, you eventually hit the same question: how much did this specific conversation cost, and how many tokens did it actually consume? The Claude API doesn't track conversations as objects — it's stateless, so every request stands alone. Tracking usage per conversation is something you have to build yourself, by capturing the usage object on every response and aggregating it against a conversation identifier you define.

This article walks through exactly how to do that: where the token counts come from, how to sum them correctly across turns (including cache reads and tool calls), and how to avoid the most common mistake — forgetting that "usage per request" and "usage per conversation" are not the same number.

Where token usage data actually comes from

Every non-streaming response from the Messages API includes a usage object:

{
  "id": "msg_01...",
  "role": "assistant",
  "content": [...],
  "usage": {
    "input_tokens": 512,
    "output_tokens": 128,
    "cache_creation_input_tokens": 0,
    "cache_read_input_tokens": 0
  }
}

input_tokens is the size of the prompt you sent on that specific call, not the cumulative size of the conversation. If you're sending the full message history on every turn (which the Messages API requires, since it's stateless), the input_tokens value grows naturally as the conversation gets longer — but it resets per request. To get a per-conversation total, you have to sum across every turn, not just read the last response.

For streaming responses, the usage data isn't available until the final event. You need to accumulate it as the stream completes rather than trying to read it mid-stream:

let usage = null;

for await (const event of stream) {
  if (event.type === "message_delta" && event.usage) {
    usage = event.usage;
  }
}

console.log(usage.input_tokens, usage.output_tokens);

Building a per-conversation tracker

The simplest reliable approach is to assign your own conversation_id at the application layer (a UUID tied to a chat session, support ticket, or document thread) and log usage against it every time you call the API.

const conversations = new Map();

function recordUsage(conversationId, usage) {
  const existing = conversations.get(conversationId) || {
    input_tokens: 0,
    output_tokens: 0,
    turns: 0,
  };

  existing.input_tokens += usage.input_tokens;
  existing.output_tokens += usage.output_tokens;
  existing.turns += 1;

  conversations.set(conversationId, existing);
}

Call recordUsage after every API response, whether streamed or not. In production you'll want this in a database table keyed by conversation ID rather than an in-memory map, especially if conversations span multiple server instances or get resumed hours later.

A few things worth getting right from the start:

Aggregating for reporting

Once usage is logged per turn, conversation-level totals are just a SUM away:

SELECT
  conversation_id,
  SUM(input_tokens) AS total_input,
  SUM(output_tokens) AS total_output,
  COUNT(*) AS turns
FROM api_usage_log
WHERE conversation_id = 'conv_8f2a...'
GROUP BY conversation_id;

This gives you per-conversation totals, but the same table supports per-user, per-day, or per-feature rollups by changing the GROUP BY and WHERE clauses — useful if you're billing customers based on their own Claude usage inside your product.

Skipping the plumbing with SubToAPI

Building and maintaining this logging layer is reasonable for a single app, but it gets repetitive fast if you're running multiple products or services against Claude, or if you have a team where several people are making calls under the same budget.

SubToAPI sits between your app and Claude: you get an application API key (sub_live_...) instead of calling Anthropic directly, and every request — streaming or not — returns usage metadata that's already tracked server-side per key, so you don't have to build the logging table yourself. If you're issuing a separate key per customer, per feature, or per team member, you get per-conversation and per-key usage visibility out of the box instead of wiring up your own aggregation pipeline.

A typical request looks the same as calling Claude directly:

curl https://api.subtoapi.app/v1/messages \
  -H "Authorization: Bearer $SUBTOAPI_KEY" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-sonnet-4",
    "max_tokens": 1024,
    "messages": [{"role": "user", "content": "Summarize this ticket."}]
  }'

The response includes the same usage object, and the dashboard rolls it up by key automatically — handy if you've already set up conversation_id tagging at the application layer and want billing-grade reporting without maintaining your own usage database. See /docs/messages for the full response shape and /docs/streaming for how usage is reported on streamed completions. Plans start at €9/month on the Solo tier, with team seats on the Team and Scale plans — details on /pricing.

questions

Does the Claude API track conversations natively? No. The API is stateless — there's no conversation object or session ID on Anthropic's side. You define the conversation boundary yourself (a UUID, a ticket number, a chat session ID) and log usage against it on every call.

Why does input_tokens grow on every turn even though I didn't add much new text? Because the Messages API requires you to resend the full message history on every request. As conversations get longer, each turn's input_tokens reflects the entire accumulated context, not just the newest message — this is also why long conversations get progressively more expensive per turn.

How do I separate cached tokens from regular input tokens in my reporting? Log cache_read_input_tokens and cache_creation_input_tokens as separate fields in your usage table rather than folding them into input_tokens. They're billed at different rates, and keeping them distinct lets you recalculate accurate costs later if pricing changes.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →