Skip to main content

Anthropic SDK Integration

Use the Anthropic SDK with AI SpendOps for accurate usage tracking of Claude models. The only change is the base URL and one extra header, and the SDK otherwise behaves exactly as it does when pointed at Anthropic directly.

Python

from anthropic import Anthropic

client = Anthropic(
api_key="sk-ant-your-key",
base_url="https://proxy.aispendops.com/v1/anthropic",
default_headers={
"X-ASO-API-Key": "aso_k_yourkey.secret",
"X-ASO-Dims": "team=ml,app=assistant",
},
)

message = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}],
)
print(message.content[0].text)

Note the base URL has no trailing /v1. The SDK appends /v1/messages itself, so adding it here produces a doubled path.

TypeScript / Node.js

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic({
apiKey: "sk-ant-your-key",
baseURL: "https://proxy.aispendops.com/v1/anthropic",
defaultHeaders: {
"X-ASO-API-Key": "aso_k_yourkey.secret",
"X-ASO-Dims": "team=backend,app=assistant",
},
});

const message = await client.messages.create({
model: "claude-sonnet-4-5-20250929",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello" }],
});
console.log(message.content[0].text);
Use the native endpoint

Always use Anthropic's native /v1/messages endpoint, which the Anthropic SDK uses by default, rather than the OpenAI-compatible /v1/chat/completions. The native endpoint returns accurate streaming usage including the cache token breakdown. The compatibility endpoint does not.

Streaming

with client.messages.stream(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[{"role": "user", "content": "Write a haiku"}],
) as stream:
for text in stream.text_stream:
print(text, end="")

Anthropic splits usage across two server-sent events rather than sending it in one block at the end. Input tokens and cache figures arrive on message_start, before any text has been generated, and output tokens arrive on message_delta as the response completes.

AI SpendOps merges both into a single usage event, so a streamed call produces exactly one record with complete numbers. This is also why Anthropic needs no stream_options injection, unlike most OpenAI-compatible providers: the usage data is already there.

Prompt caching

Prompt caching is where the Anthropic SDK and AI SpendOps interact most usefully, because caching changes what a request costs by a large factor and the saving is invisible unless something is measuring it.

message = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
system=[
{
"type": "text",
"text": LONG_SYSTEM_PROMPT,
"cache_control": {"type": "ephemeral"},
}
],
messages=[{"role": "user", "content": "Hello"}],
)

Every AI SpendOps usage event records cache_read_tokens and cache_write_tokens separately from prompt_tokens, and tracks the 1-hour cache write tier apart from the 5-minute one because Anthropic prices them differently. That lets you answer the question caching actually raises, which is whether the cache is being hit often enough to pay for the writes.

Compare cache_read_tokens against cache_write_tokens over a period. Heavy writes with few reads means the cache is expiring before it is reused, and you are paying a premium for nothing. Full detail is on the Anthropic provider page.

Notes

  • Set X-ASO-Dims per request rather than on the client if one service handles work for several teams, features, or customers. See Dimensions.
  • The anthropic-version header passes through unchanged, as does anthropic-beta.
  • You keep using your own Anthropic key. AI SpendOps never replaces or stores provider credentials, and removing the base_url returns you to calling Anthropic directly.

Runnable examples

Complete Anthropic projects, including streaming with cache-token reporting, are in the open-source examples repo: github.com/AI-SpendOps/examples. See python/anthropic_chat.py and python/anthropic_streaming.py.