Track cost and usage changedagent-sdk/cost-tracking
Nearest release: v2.1.290, published 3 hours before upstream edited the page. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.
Upstream edited this page at 5 Oct 2026 22:11 UTC, give or take a minute or two: the time comes from Anthropic’s own sitemap rather than from a commit. This site recorded the change at 20 Oct 2026 18:37 UTC.
Upstream edited
Recorded here
Lines+160added
Lines−172removed
From line
6
where the diff opens
First seen
14 Aug 2026
this site's first read of the page
Recorded edits11to this page, all time
The whole hunk
from line 6, old and new numbered
/
from line 6
66
77For complete API documentation, see the [TypeScript SDK reference](/docs/en/agent-sdk/typescript) and [Python SDK reference](/docs/en/agent-sdk/python).
88
9<span id="estimates-not-billing" />
9> [!WARNING]
10>
11> 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, unless a [`modelPricing`](/docs/en/settings-reference#modelpricing) table is in effect. They can drift from what you are actually billed when:
12>
13> * pricing changes
14> * the installed SDK version does not recognize a model
15> * billing rules apply that the client cannot model
16>
17> One billing rule the SDK does model is [data residency pricing](https://platform.claude.com/docs/en/about-claude/pricing#data-residency-pricing). When a response's `usage` reports `inference_geo: "us"`, the SDK multiplies the list price of that response's tokens by 1.1. Per-request fees such as web search aren't multiplied. Requires TypeScript Agent SDK v0.3.239 or later, or Python Agent SDK v0.2.144 or later.
18>
19> Use these fields for development insight and approximate budgeting. For authoritative billing, use the [Usage and Cost API](https://platform.claude.com/docs/en/build-with-claude/usage-cost-api) or the Usage page in the [Claude Console](https://platform.claude.com/usage). Do not bill end users or trigger financial decisions from these fields.
1020
11<Warning>
12 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, unless a [`modelPricing`](/docs/en/settings-reference#modelpricing) table is in effect. They can drift from what you are actually billed when:
13
14 * pricing changes
15 * the installed SDK version does not recognize a model
16 * billing rules apply that the client cannot model
17
18 One billing rule the SDK does model is [data residency pricing](https://platform.claude.com/docs/en/about-claude/pricing#data-residency-pricing). When a response's `usage` reports `inference_geo: "us"`, the SDK multiplies the list price of that response's tokens by 1.1. Per-request fees such as web search aren't multiplied. Requires TypeScript Agent SDK v0.3.239 or later, or Python Agent SDK v0.2.144 or later.
19
20 Use these fields for development insight and approximate budgeting. For authoritative billing, use the [Usage and Cost API](https://platform.claude.com/docs/en/build-with-claude/usage-cost-api) or the Usage page in the [Claude Console](https://platform.claude.com/usage). Do not bill end users or trigger financial decisions from these fields.
21</Warning>
22
2321## Understand token usage
2422
2523The TypeScript and Python SDKs expose the same usage data with different field names:
from line 35
3735
3836The 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:
3937
40<img src="https://mintcdn.com/claude-code/ikqp3_70mqIahteV/images/agent-sdk/message-usage-flow.svg?fit=max&auto=format&n=ikqp3_70mqIahteV&q=85&s=68497aee338e01cc745323af7aea378e" className="dark:hidden" alt="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." width="760" height="520" data-path="images/agent-sdk/message-usage-flow.svg" />
38
4139
42<img src="https://mintcdn.com/claude-code/_xqph1dUOslCOwsj/images/agent-sdk/message-usage-flow-dark.svg?fit=max&auto=format&n=_xqph1dUOslCOwsj&q=85&s=8ea95085abc0a6b7f55ecef498bd4d14" className="hidden dark:block" alt="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." width="760" height="520" data-path="images/agent-sdk/message-usage-flow-dark.svg" />
40**1. Each step produces assistant messages**
4341
44<Steps>
45 <Step title="Each step produces assistant messages">
46 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`](https://platform.claude.com/docs/en/api/messages) 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.
47 </Step>
42When 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`](https://platform.claude.com/docs/en/api/messages) 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.
4843
49 <Step title="The result message provides the cumulative estimate">
50 When the `query()` call completes, the SDK emits a result message with `total_cost_usd` and cumulative `usage`, typed as [`SDKResultMessage`](/docs/en/agent-sdk/typescript#sdkresultmessage) in TypeScript and [`ResultMessage`](/docs/en/agent-sdk/python#resultmessage) in Python. If you only need the estimated total, you can ignore the per-step usage and read this single value.
44**2. The result message provides the cumulative estimate**
5145
52 If you make multiple independent `query()` calls, each result reflects only the cost of that individual call. A call that resumes a session also counts the session's earlier spend.
46When the `query()` call completes, the SDK emits a result message with `total_cost_usd` and cumulative `usage`, typed as [`SDKResultMessage`](/docs/en/agent-sdk/typescript#sdkresultmessage) in TypeScript and [`ResultMessage`](/docs/en/agent-sdk/python#resultmessage) in Python. If you only need the estimated total, you can ignore the per-step usage and read this single value.
5347
54 In streaming input mode, each turn emits its own result message. See [Track costs in streaming input mode](#track-costs-in-streaming-input-mode) for how to read call totals in that mode.
55 </Step>
56</Steps>
48If you make multiple independent `query()` calls, each result reflects only the cost of that individual call. A call that resumes a session also counts the session's earlier spend.
5749
50In streaming input mode, each turn emits its own result message. See [Track costs in streaming input mode](#track-costs-in-streaming-input-mode) for how to read call totals in that mode.
51
5852## Track costs in streaming input mode
5953
6054In [streaming input mode](/docs/en/agent-sdk/streaming-vs-single-mode), one `query()` call carries multiple user turns and each turn emits its own result message. The result fields differ in scope:
from line 91
9791
9892The following examples iterate over the message stream from a `query()` call and print the total cost when the `result` message arrives:
9993
100<CodeGroup>
101 ```typescript TypeScript theme={null}
102 import { query } from "@anthropic-ai/claude-agent-sdk";
94```typescript TypeScript theme={null}
95import { query } from "@anthropic-ai/claude-agent-sdk";
10396
104 try {
105 for await (const message of query({ prompt: "Summarize this project" })) {
106 if (message.type === "result") {
107 console.log(`Total cost: $${message.total_cost_usd}`);
108 }
97try {
98 for await (const message of query({ prompt: "Summarize this project" })) {
99 if (message.type === "result") {
100 console.log(`Total cost: $${message.total_cost_usd}`);
109101 }
110 } catch (error) {
111 // A single-shot query() throws after yielding an error result. If the
112 // failure was an error result, it still carried total_cost_usd and the
113 // branch above has already run; connection or process failures yield
114 // no result message.
115 console.error(`Session ended with an error: ${error}`);
116102 }
117 ```
103} catch (error) {
104 // A single-shot query() throws after yielding an error result. If the
105 // failure was an error result, it still carried total_cost_usd and the
106 // branch above has already run; connection or process failures yield
107 // no result message.
108 console.error(`Session ended with an error: ${error}`);
109}
110```
118111
119 ```python Python theme={null}
120 from claude_agent_sdk import query, ResultMessage
121 import asyncio
112```python Python theme={null}
113from claude_agent_sdk import query, ResultMessage
114import asyncio
122115
123116
124 async def main():
125 try:
126 async for message in query(prompt="Summarize this project"):
127 if isinstance(message, ResultMessage):
128 print(f"Total cost: ${message.total_cost_usd or 0}")
129 except Exception as error:
130 # A single-shot query() raises after yielding an error result. If the
131 # failure was an error result, the branch above has already run;
132 # connection or process failures yield no result message.
133 print(f"Session ended with an error: {error}")
117async def main():
118 try:
119 async for message in query(prompt="Summarize this project"):
120 if isinstance(message, ResultMessage):
121 print(f"Total cost: ${message.total_cost_usd or 0}")
122 except Exception as error:
123 # A single-shot query() raises after yielding an error result. If the
124 # failure was an error result, the branch above has already run;
125 # connection or process failures yield no result message.
126 print(f"Session ended with an error: {error}")
134127
135128
136 asyncio.run(main())
137 ```
138</CodeGroup>
129asyncio.run(main())
130```
139131
140132To bound how much subagents can add to `total_cost_usd`, set the [depth, concurrency, and spend limits](/docs/en/agent-sdk/subagents#cap-subagent-depth-concurrency-and-spend) on the query.
141133
from line 139
147139
148140Each 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.
149141
150<Warning>
151 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](#read-output-tokens-from-the-result-message).
152</Warning>
142> [!WARNING]
143>
144> 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](#read-output-tokens-from-the-result-message).
153145
154146The 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:
155147
from line 224
232224
233225The 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:
234226
235<CodeGroup>
236 ```typescript TypeScript theme={null}
237 import { query } from "@anthropic-ai/claude-agent-sdk";
227```typescript TypeScript theme={null}
228import { query } from "@anthropic-ai/claude-agent-sdk";
238229
239 // Track cumulative cost across multiple query() calls
240 let totalSpend = 0;
230// Track cumulative cost across multiple query() calls
231let totalSpend = 0;
241232
242 const prompts = [
243 "Read the files in src/ and summarize the architecture",
244 "List all exported functions in src/auth.ts"
245 ];
233const prompts = [
234 "Read the files in src/ and summarize the architecture",
235 "List all exported functions in src/auth.ts"
236];
246237
247 for (const prompt of prompts) {
248 try {
249 for await (const message of query({ prompt })) {
250 if (message.type === "result") {
251 totalSpend += message.total_cost_usd;
252 console.log(`This call: $${message.total_cost_usd}`);
253 }
238for (const prompt of prompts) {
239 try {
240 for await (const message of query({ prompt })) {
241 if (message.type === "result") {
242 totalSpend += message.total_cost_usd;
243 console.log(`This call: $${message.total_cost_usd}`);
254244 }
255 } catch (error) {
256 // A single-shot query() throws after yielding an error result. If the
257 // failure was an error result, this call's cost was already counted;
258 // connection or process failures yield no result message. Continue
259 // with the next prompt.
260 console.error(`Call failed: ${error}`);
261245 }
246 } catch (error) {
247 // A single-shot query() throws after yielding an error result. If the
248 // failure was an error result, this call's cost was already counted;
249 // connection or process failures yield no result message. Continue
250 // with the next prompt.
251 console.error(`Call failed: ${error}`);
262252 }
253}
263254
264 console.log(`Total spend: $${totalSpend.toFixed(4)}`);
265 ```
255console.log(`Total spend: $${totalSpend.toFixed(4)}`);
256```
266257
267 ```python Python theme={null}
268 from claude_agent_sdk import query, ResultMessage
269 import asyncio
258```python Python theme={null}
259from claude_agent_sdk import query, ResultMessage
260import asyncio
270261
271262
272 async def main():
273 # Track cumulative cost across multiple query() calls
274 total_spend = 0.0
263async def main():
264 # Track cumulative cost across multiple query() calls
265 total_spend = 0.0
275266
276 prompts = [
277 "Read the files in src/ and summarize the architecture",
278 "List all exported functions in src/auth.ts",
279 ]
267 prompts = [
268 "Read the files in src/ and summarize the architecture",
269 "List all exported functions in src/auth.ts",
270 ]
280271
281 for prompt in prompts:
282 try:
283 async for message in query(prompt=prompt):
284 if isinstance(message, ResultMessage):
285 cost = message.total_cost_usd or 0
286 total_spend += cost
287 print(f"This call: ${cost}")
288 except Exception as error:
289 # A single-shot query() raises after yielding an error result. If
290 # the failure was an error result, this call's cost was already
291 # counted; connection or process failures yield no result message.
292 # Continue with the next prompt.
293 print(f"Call failed: {error}")
272 for prompt in prompts:
273 try:
274 async for message in query(prompt=prompt):
275 if isinstance(message, ResultMessage):
276 cost = message.total_cost_usd or 0
277 total_spend += cost
278 print(f"This call: ${cost}")
279 except Exception as error:
280 # A single-shot query() raises after yielding an error result. If
281 # the failure was an error result, this call's cost was already
282 # counted; connection or process failures yield no result message.
283 # Continue with the next prompt.
284 print(f"Call failed: {error}")
294285
295 print(f"Total spend: ${total_spend:.4f}")
286 print(f"Total spend: ${total_spend:.4f}")
296287
297288
298 asyncio.run(main())
299 ```
300</CodeGroup>
289asyncio.run(main())
290```
301291
302292## Handle errors, caching, and output token counts
303293
from line 341
351341
352342The 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](/docs/en/amazon-bedrock); without them the query fails.
353343
354<CodeGroup>
355 ```python Python theme={null}
356 from claude_agent_sdk import ClaudeAgentOptions, query
357 import asyncio
344```python Python theme={null}
345from claude_agent_sdk import ClaudeAgentOptions, query
346import asyncio
358347
359348
360 async def main():
361 options = ClaudeAgentOptions(
362 env={
363 "CLAUDE_CODE_USE_BEDROCK": "1",
364 "ENABLE_PROMPT_CACHING_1H": "1",
365 },
366 )
349async def main():
350 options = ClaudeAgentOptions(
351 env={
352 "CLAUDE_CODE_USE_BEDROCK": "1",
353 "ENABLE_PROMPT_CACHING_1H": "1",
354 },
355 )
367356
368 async for message in query(prompt="Summarize this project", options=options):
369 print(message)
357 async for message in query(prompt="Summarize this project", options=options):
358 print(message)
370359
371360
372 asyncio.run(main())
373 ```
361asyncio.run(main())
362```
374363
375 ```typescript TypeScript theme={null}
376 import { query } from "@anthropic-ai/claude-agent-sdk";
364```typescript TypeScript theme={null}
365import { query } from "@anthropic-ai/claude-agent-sdk";
377366
378 const options = {
379 env: {
380 ...process.env,
381 CLAUDE_CODE_USE_BEDROCK: "1",
382 ENABLE_PROMPT_CACHING_1H: "1",
383 },
384 };
367const options = {
368 env: {
369 ...process.env,
370 CLAUDE_CODE_USE_BEDROCK: "1",
371 ENABLE_PROMPT_CACHING_1H: "1",
372 },
373};
385374
386 for await (const message of query({ prompt: "Summarize this project", options })) {
387 console.log(message);
388 }
389 ```
390</CodeGroup>
375for await (const message of query({ prompt: "Summarize this project", options })) {
376 console.log(message);
377}
378```
391379
392380Cache writes with a 1-hour TTL are billed at a higher rate than 5-minute writes, so enabling this trades higher write cost for more cache reads. See [prompt caching pricing](https://platform.claude.com/docs/en/build-with-claude/prompt-caching) for details. On a Claude subscription within your plan's included usage, you get the 1-hour TTL on your own turns, and on some of the helper requests Claude Code makes beside them, without setting this variable, and Claude Code drops those turns to the 5-minute TTL once you're drawing on [usage credits](https://support.claude.com/en/articles/12429409-extra-usage-for-paid-claude-plans).
393381
No line in this hunk matches that.