AI API Cost Calculator

Calculate and Compare
AI API Costs

Estimate your AI spending, compare model pricing, and understand what your real-world workloads actually cost.

11Pricing guides
8Models in calculator
Oct 2026Prices verified

Anthropic Cost Tracking: Build a Claude Usage Logger in Python

Anthropic Cost Tracking: Build a Claude Usage Logger in Python

Anthropic cost tracking is a small wrapper around your Claude API calls that records, for every single request, how many fresh input tokens you sent, how many tokens you paid to write into the prompt cache, how many you read back from it, how many output tokens the model produced, and what that request cost under the rates you configure. Put it between your code and the Anthropic client, log one line per call to a file, and the next time a number on the Claude Console looks wrong, you can point at the exact feature, user, or prompt that produced it. If you already built the OpenAI version of this wrapper, this is its Claude twin — the same sixty-line shape, but the usage object it reads is different in exactly the way that makes Claude bills confusing.

That difference is the whole reason this tutorial exists. OpenAI reports one prompt-token count and tells you how many of those tokens were cached. Claude reports three separate input numbers, and each one is priced differently. Miss one of the three and your log quietly under-reports your bill — usually by the most expensive line on it.

Claude splits your input three ways, and each way has its own price

Every Claude Messages API response carries a usage object with four token counts that matter for pricing: input_tokens for fresh input, cache_creation_input_tokens for tokens written into the prompt cache, cache_read_input_tokens for tokens read back from it, and output_tokens for everything the model generated. The first three are all "input", but Anthropic prices them as three different things. On Claude Sonnet 5.5, verified 10 November 2026 on Anthropic's pricing page, fresh input costs $2.00 per million tokens, a 5-minute cache write costs $2.50, a 1-hour write costs $4.00, and a cache read costs $0.10. Output is $10.00 per million.

Take one call on Sonnet 5.5 with 10,000 fresh input tokens, a 50,000-token system prompt written to a 5-minute cache, 40,000 tokens read back from that cache, and 1,000 output tokens. The arithmetic on the published rates is: fresh input 10,000 × $2.00 ÷ 1,000,000 = $0.02; cache write 50,000 × $2.50 ÷ 1,000,000 = $0.125; cache read 40,000 × $0.10 ÷ 1,000,000 = $0.004; output 1,000 × $10.00 ÷ 1,000,000 = $0.01. Total: $0.159. The write alone is nearly eighty percent of the call, and it is the line most homemade loggers never record, because the field that carries it does not exist in OpenAI's usage object.

Notice what the same call costs the first time it runs, before any cache exists: 60,000 fresh input tokens at $2.00 per million is $0.12, plus $0.01 of output, for $0.13. The cached call at $0.159 is dearer than the uncached one, because the write fee is paid before the read discount starts. That is the surcharge our cache-write pricing investigation worked through in detail — the wrapper below is how you catch it happening on your own traffic instead of reading about it in an article.

The code: a wrapper you can drop in

Install the SDK with pip install anthropic and set ANTHROPIC_API_KEY in your environment. Save the code below as claude_tracker.py next to your app, and call track_message() instead of calling the client directly. It appends one JSON line per call to claude_costs.jsonl, stores no prompt or answer text, and returns the original response untouched, so the rest of your code does not change.

import json
import time
from datetime import datetime, timezone

from anthropic import Anthropic

# Rates per 1M tokens for Claude Sonnet 5.5, verified 10 November 2026
# on Anthropic's pricing page (docs.claude.com/en/docs/about-claude/pricing).
# If you use a different model, replace these with its row from that page.
SONNET_55_RATES = {
    "input_per_million": 2.00,           # fresh input tokens
    "cache_write_5m_per_million": 2.50,  # 5-minute cache write
    "cache_write_1h_per_million": 4.00,  # 1-hour cache write
    "cache_read_per_million": 0.10,      # cached input tokens read back
    "output_per_million": 10.00,         # output tokens
}

client = Anthropic()  # reads ANTHROPIC_API_KEY from the environment


def _cache_write_tokens(usage):
    """Cache write tokens, handling both the flat field and the
    breakdown object newer API versions return."""
    direct = getattr(usage, "cache_creation_input_tokens", None)
    if direct:
        return direct
    breakdown = getattr(usage, "cache_creation", None)
    if breakdown is not None:
        return (
            getattr(breakdown, "ephemeral_5m_input_tokens", 0)
            + getattr(breakdown, "ephemeral_1h_input_tokens", 0)
        )
    return 0


def _cost_usd(usage, rates, cache_ttl="5m"):
    fresh = usage.input_tokens or 0
    write = _cache_write_tokens(usage)
    read = getattr(usage, "cache_read_input_tokens", 0) or 0
    output = usage.output_tokens or 0
    write_rate = (
        rates["cache_write_1h_per_million"]
        if cache_ttl == "1h"
        else rates["cache_write_5m_per_million"]
    )
    cost = (
        fresh * rates["input_per_million"]
        + write * write_rate
        + read * rates["cache_read_per_million"]
        + output * rates["output_per_million"]
    ) / 1_000_000
    return fresh, write, read, output, cost


def track_message(model, messages, feature="unknown",
                  rates=SONNET_55_RATES, cache_ttl="5m", **kwargs):
    """Drop-in replacement for client.messages.create."""
    started = time.monotonic()
    response = client.messages.create(
        model=model, messages=messages, **kwargs
    )
    latency_ms = round((time.monotonic() - started) * 1000)

    record = {
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "feature": feature,
        "model": model,
        "latency_ms": latency_ms,
    }
    usage = getattr(response, "usage", None)
    if usage is not None:
        fresh, write, read, output, cost = _cost_usd(
            usage, rates, cache_ttl
        )
        record.update({
            "input_tokens": fresh,
            "cache_write_tokens": write,
            "cache_read_tokens": read,
            "output_tokens": output,
            "cost_usd": round(cost, 6),
        })
    else:
        record["note"] = "no usage object returned"

    with open("claude_costs.jsonl", "a", encoding="utf-8") as f:
        f.write(json.dumps(record) + "\n")
    return response

Using it is one line different from a normal call. Replace client.messages.create(model=..., messages=...) with track_message("your-model-id", messages, feature="support_reply", max_tokens=1024) — the Messages API requires max_tokens, so pass it through as before. The feature label is the most important parameter in the file: it is what you will group by later, so name it after your product ("support_reply", "summariser", "nightly_report"), not your code.

One parameter deserves a second look: cache_ttl. The usage object tells you how many tokens were written to the cache, but not which lifetime you asked for, and a 1-hour write costs 60 percent more than a 5-minute one on Sonnet 5.5. The wrapper cannot detect the lifetime, so you declare it: pass cache_ttl="1h" when your request set a one-hour cache breakpoint, and leave the default when it did not. If you get this wrong, the token counts stay right and only the dollar column is off — which is still better than not logging the write at all, the mistake this wrapper exists to prevent.

Reading one log line

After a few calls, claude_costs.jsonl holds lines shaped like the worked example above, with the numbers shown only to illustrate the shape of a record:

{"timestamp": "2026-11-10T05:00:00+00:00", "feature": "support_reply",
 "model": "your-model-id", "latency_ms": 2130, "input_tokens": 10000,
 "cache_write_tokens": 50000, "cache_read_tokens": 40000,
 "output_tokens": 1000, "cost_usd": 0.159}

Read it from right to left. The cost is the headline, but the three input numbers are the diagnosis. Here the write dwarfs everything else, which is expected on a first call that fills the cache — the next call on the same prompt should show the write near zero and the read near 50,000, dropping the same request to roughly $0.034. If instead the write stays high day after day, the cache is never being hit: something is changing the prompt prefix on every call, perhaps a timestamp or a user ID injected at the top, and every call is paying the write fee for a discount it never collects. That pattern is invisible on the Console, where the three input buckets are folded into one total, and it is the single most valuable thing this log will ever show you.

Two Claude-specific traps that make logs lie

The first trap is extended thinking. If you enable thinking on a Claude call, the tokens the model spends thinking are billed as output tokens — they arrive inside output_tokens, not as a separate field. A short-looking answer can therefore carry a long hidden output count, exactly like the reasoning tokens we investigated on OpenAI. The wrapper records the total honestly, because it reads the field the API returns, but if you ever wonder why a feature's output column doubled overnight, check whether someone turned thinking on before blaming the model or the prompt.

The second trap is streaming. A streamed Claude response does not hand you a complete usage object in the normal way. With the Python SDK, wrap the call in the streaming helper and read the usage from the final message: with client.messages.stream(model=..., messages=..., max_tokens=...) as stream: message = stream.get_final_message(), then pass message.usage through the same _cost_usd function. If you wrap a streaming call with the code above and never see usage, that is why — the stream simply never sent the numbers to the place you looked. Never log per chunk: you would count one call many times over, and your totals would be fiction.

Turn the log into a daily summary, not a spreadsheet

A log file nobody reads is decoration. The wrapper earns its keep when a tiny script reads it once a day and tells you only what changed. Start with three summaries, and nothing more: total cost per feature for the last 24 hours, the ten most expensive single calls, and any feature whose daily cost doubled compared with its seven-day average. The first tells you where the money goes, the second tells you what to fix next, and the third is the early warning that catches a runaway job before the month ends.

When a summary surprises you, the log already holds the feature, model, token split, and timestamp you need to act — and if the surprise is one user rather than one feature, the same per-call records feed directly into the per-user spending guard we built for that exact problem. To sanity-check the totals against your own token mix, our AI API Cost Calculator lets you plug in your own numbers and compare them with what the log reports.

That measurement habit is the real point of this tutorial. Pricing pages tell you what a token costs; only your own log tells you what your app actually spends tokens on — and on Claude, "what it spends" is three different input prices hiding inside one total. Build the wrapper this week, let it run for seven days without changing anything else, and read the per-feature summary before you change a single prompt or model. The numbers will almost certainly surprise you, and unlike a dashboard total, they will tell you exactly what to do next.

Rates in this tutorial were verified 10 November 2026 on Anthropic's pricing documentation (docs.claude.com/en/docs/about-claude/pricing). Usage fields follow Anthropic's Messages API: input_tokens, cache_creation_input_tokens, cache_read_input_tokens, and output_tokens on the response usage object. Rates change — re-check the pricing page whenever you change models, and never trust a dollar figure copied from a tutorial over the provider's own page.

Popular posts from this blog

DeepSeek API Pricing Explained: What a Chatbot Actually Costs in 2026

OpenAI API Pricing Explained: Every Model, Every Hidden Cost

Claude Opus 5.5 API Pricing: Per-Token Costs and Workload Math