Skip to main content
The Claude Agent SDK provides detailed token usage information for each interaction with Claude. This guide explains how to properly track usage and understand cost reporting, especially when dealing with parallel tool uses and multi-step conversations. For complete API documentation, see the TypeScript SDK reference and Python SDK reference.
The total_cost_usd and costUSD fields are client-side estimates, not authoritative billing data. The SDK computes them locally from a price table bundled at build time, so they can drift from what you are actually billed when:
  • pricing changes
  • the installed SDK version does not recognize a model
  • billing rules apply that the client cannot model
Use these fields for development insight and approximate budgeting. For authoritative billing, use the Usage and Cost API or the Usage page in the Claude Console. Do not bill end users or trigger financial decisions from these fields.

Understand token usage

The TypeScript and Python SDKs expose the same usage data with different field names:
  • TypeScript provides per-step token breakdowns on each assistant message (message.message.id, message.message.usage), per-model cost via modelUsage on the result message, and a cumulative total on the result message.
  • Python provides per-step token breakdowns on each assistant message as message.usage and message.message_id, per-model cost via model_usage on the result message, and the cumulative total on the result message as total_cost_usd.
Both SDKs use the same underlying cost model and expose the same granularity. The difference is in field naming and where per-step usage is nested. Cost tracking depends on understanding how the SDK scopes usage data:
  • query() call: one invocation of the SDK’s query() function. A single call can involve multiple steps: Claude responds, uses tools, gets results, and responds again. Each call produces one result message at the end, except in streaming input mode, where one query() call carries multiple user turns and each turn emits its own result message.
  • Step: a single request/response cycle within a query() call. Each step produces assistant messages with token usage.
  • Session: a series of query() calls linked by a session ID (using the resume option). Each query() call within a session reports its own cost independently.
The following diagram shows the message stream from a single query() call, with token usage reported at each step and the cumulative estimate at the end: Diagram showing a query producing two steps of messages. Step 1 has four assistant messages sharing the same ID and usage (count once), Step 2 has one assistant message with a new ID, and the final result message shows the estimated total_cost_usd.
1

Each step produces assistant messages

When Claude responds, it sends one or more assistant messages. In TypeScript, each assistant message contains a nested BetaMessage (accessed via message.message) with an id and a usage object with token counts (input_tokens, output_tokens). In Python, the AssistantMessage dataclass exposes the same data directly via message.usage and message.message_id. When Claude uses multiple tools in one turn, all messages in that turn share the same ID, so deduplicate by ID to avoid double-counting.
2

The result message provides the cumulative estimate

When the query() call completes, the SDK emits a result message with total_cost_usd and cumulative usage, typed as SDKResultMessage in TypeScript and ResultMessage in Python. If you make multiple query() calls, for example in a multi-turn session, each result reflects only the cost of that individual call. If you only need the estimated total, you can ignore the per-step usage and read this single value.In streaming input mode, each turn emits its own result message. See Track costs in streaming input mode for how to read call totals in that mode.

Track costs in streaming input mode

In streaming input mode, one query() call carries multiple user turns and each turn emits its own result message. The result fields differ in scope:
  • usage: covers only that turn, and within it only the main agent loop, not any subagents it ran.
  • total_cost_usd and modelUsage, or model_usage in Python: carry the running total for the whole call so far.
In a call where your app never sends /clear, /reset, or /new, read the latest result for call totals rather than summing across results. The running totals start over each time your app sends one of those three commands, and inside a query() call nothing else resets them. Three results matter for your accounting:
  • The /clear turn’s own result: covers only what has run since the reset, and carries a new session_id.
  • Every later result: keeps counting from that reset.
  • The last result before each /clear: holds the total for the turns since the previous reset.
To total the whole call, add the last result from before each /clear to the call’s final result. Every other result, including the /clear turn’s own, is superseded by a later one. In TypeScript, the SDK also emits an SDKConversationResetMessage at each reset, so you can detect resets from the stream. In Python, the SDK likewise emits a ConversationResetMessage. Before Python SDK v0.2.137, the Python iterator dropped that message, so on those versions count the resets yourself from the /clear turns your app sends. maxBudgetUsd, or max_budget_usd in Python, is compared against the same running total, so a /clear also starts the budget over.

Get the total cost of a query

The result message, typed as SDKResultMessage in TypeScript and ResultMessage in Python, marks the end of the agent loop for a query() call. It includes total_cost_usd, the cumulative estimated cost across all steps in that call. In Python the field is typed as optional, so check that it isn’t None before you read it. Success and error results both carry it, though the final result of a session crash may carry it zeroed. If you use sessions to make multiple query() calls, each result reflects only the cost of that individual call. In streaming input mode, read call totals as described in Track costs in streaming input mode. The three result-level fields differ in what they count when the agent spawns subagents. Use modelUsage, or model_usage in Python, for whole-tree token accounting; the usage field undercounts as soon as nesting occurs. The following examples iterate over the message stream from a query() call and print the total cost when the result message arrives:
To bound how much subagents can add to total_cost_usd, set the depth, concurrency, and spend limits on the query.

Track per-step and per-model usage

The examples in this section use TypeScript field names. In Python, the equivalent fields are AssistantMessage.usage and AssistantMessage.message_id for per-step usage, and ResultMessage.model_usage for per-model breakdowns.

Track per-step usage

Each assistant message contains a nested BetaMessage (accessed via message.message) with an id and usage object with token counts. When Claude uses tools in parallel, multiple messages share the same id with identical usage data. Track which IDs you’ve already counted and skip duplicates to avoid inflated totals.
The deduplicated per-step values are accurate for input and cache tokens. Per-step output_tokens is a placeholder, so read output tokens from the result message.
The following example accumulates input tokens across all steps, counting each unique main-loop message ID only once and skipping subagent messages, and reads the output total from the result message, which covers the main loop:

Break down usage per model

The result message includes modelUsage, a map of model name to per-model token counts and cost. This is useful when you run multiple models (for example, Haiku for subagents and Opus for the main agent) and want to see where tokens are going. The following example runs a query and prints the cost and token breakdown for each model used:

Accumulate costs across multiple calls

Each query() call returns its own total_cost_usd. The SDK doesn’t provide a session-level total, so if your application makes multiple query() calls, for example in a multi-turn session or across different users, accumulate the totals yourself. In streaming input mode, read each call’s total as described in Track costs in streaming input mode. For a call that ended in a crash, see Recover totals after a session crash. The following examples run two query() calls sequentially, add each call’s total_cost_usd to a running total, and print both the per-call and combined cost: