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 viamodelUsageon the result message, and a cumulative total on the result message. - Python provides per-step token breakdowns on each assistant message as
message.usageandmessage.message_id, per-model cost viamodel_usageon the result message, and the cumulative total on the result message astotal_cost_usd.
query()call: one invocation of the SDK’squery()function. A single call can involve multiple steps: Claude responds, uses tools, gets results, and responds again. Each call produces oneresultmessage at the end, except in streaming input mode, where onequery()call carries multiple user turns and each turn emits its ownresultmessage.- 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 theresumeoption). Eachquery()call within a session reports its own cost independently.
query() call, with token usage reported at each step and the cumulative estimate at the end:
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, onequery() 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_usdandmodelUsage, ormodel_usagein Python: carry the running total for the whole call so far.
/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
/clearturn’s own result: covers only what has run since the reset, and carries a newsession_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.
/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 asSDKResultMessage 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:
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 areAssistantMessage.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 nestedBetaMessage (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 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 includesmodelUsage, 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
Eachquery() 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:
Handle errors, caching, and output token counts
For accurate cost tracking, account for the placeholder output count on assistant messages, the tokens a failed conversation consumed, and cache token pricing.Read output tokens from the result message
Claude Code builds each assistant message from the usage the API reported when the response began, so the message’soutput_tokens is only the count the API had reported at message_start, before the response was generated. One API response can produce several assistant messages, and every one of them carries that same placeholder.
The API reports the real output count at the end of the response, and Claude Code adds it to the result message. Read output tokens from the result’s usage, or from modelUsage for a per-model breakdown.
To watch a response’s output count grow while it streams, set includePartialMessages, or include_partial_messages in Python, and read usage from each message_delta stream event, typed as SDKPartialAssistantMessage in TypeScript and StreamEvent in Python.
Track costs on failed conversations
Both success and error result messages includeusage and total_cost_usd; in Python both fields are typed as optional, so check that they aren’t None before you read them.
If a conversation fails midway, you still consumed tokens up to the point of failure. Read cost data from every result message, whether its subtype is success or one of the error subtypes. On some error results, usage reports less than the call spent:
error_during_executionafter a session crash: every cost field may be zeroed.error_max_budget_usd:usageleaves out the response that crossed the budget, whiletotal_cost_usdandmodelUsageinclude it.
total_cost_usd or modelUsage rather than usage.
Recover totals after a session crash
When the Claude Code process crashes, it emits a finalerror_during_execution result and exits, in single-shot and streaming input mode alike. That result may carry zeroed usage, total_cost_usd, and modelUsage, so recover the call’s totals from what arrived before it. Step 1 recovers the full totals whenever an earlier result exists; the fallback in step 2 recovers only the main loop’s input and cache tokens.
- Use the result of the turn before the crash. In streaming input mode, it holds the running total since the start of the call or since the last
/clear. Go to step 2 instead when that result can’t help you:- The call was single-shot, so no earlier result exists.
- The crash happened on the first turn.
- The turn before the crash was the
/clearitself, so its result covers only the reset.
- Sum the
usageon the assistant messages instead, counting each API response once, as the Track per-step usage example does. In single-shot mode, sum all of them; in streaming input mode, sum the ones that arrived after the last result. This gives you the main loop’s input and cache tokens. Subagent usage isn’t recoverable this way, and neither are output tokens or USD cost, because per-stepoutput_tokensis a placeholder.
Track cache tokens
The Agent SDK automatically uses prompt caching to reduce costs on repeated content. You do not need to configure caching yourself. The usage object includes two additional fields for cache tracking:cache_creation_input_tokens: tokens used to create new cache entries (charged at a higher rate than standard input tokens).cache_read_input_tokens: tokens read from existing cache entries (charged at a reduced rate).
input_tokens to understand caching savings. In TypeScript, these fields are typed on the Usage object. In Python, they appear as keys in the ResultMessage.usage dict (for example, message.usage.get("cache_read_input_tokens", 0)).
Extend the prompt cache TTL to one hour
Cache entries written by the SDK use a 5-minute TTL by default when you authenticate with an API key or run on Amazon Bedrock, Google Cloud’s Agent Platform, Microsoft Foundry, or Claude Platform on AWS. If your workload runs many short sessions against the same system prompt and context with gaps longer than 5 minutes between them, the cache expires between sessions and each new session pays full input price. To request a 1-hour TTL on cache writes, set theENABLE_PROMPT_CACHING_1H environment variable. You can export it in your shell or container environment, or pass it through options.env.
The following example enables 1-hour TTL for an agent running on Amazon Bedrock. Because it sets CLAUDE_CODE_USE_BEDROCK, it requires working AWS credentials for Amazon Bedrock; without them the query fails.
ENABLE_PROMPT_CACHING_1H.
Related documentation
- TypeScript SDK Reference - Complete API documentation
- SDK Overview - Getting started with the SDK
- SDK Permissions - Managing tool permissions