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);
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-Dimsper request rather than on the client if one service handles work for several teams, features, or customers. See Dimensions. - The
anthropic-versionheader passes through unchanged, as doesanthropic-beta. - You keep using your own Anthropic key. AI SpendOps never replaces or stores provider credentials, and removing the
base_urlreturns 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.