Read Cache Usage

Su Suriya Ravichandran Updated 10 Nov 2026
6 min read ·Lesson 121 of 200

Three Fields, One Total

Caching changes how input tokens are counted, so the usage fields need careful reading. Every response reports three input values (Lesson 44 introduced the basics):

FieldMeaning
input_tokensInput after the last breakpoint, which was not cached
cache_creation_input_tokensInput written to the cache by this request
cache_read_input_tokensInput read from the cache by this request

The total input for the request is the sum of all three:

total input = cache_read_input_tokens + cache_creation_input_tokens + input_tokens

The most common mistake is to read input_tokens as the total. It is only the uncached tail of the prompt. With a large cached document, input_tokens can be a tiny fraction of what Claude actually read.


What Each Field Costs

Each field is priced differently (Lesson 121):

FieldPrice relative to base input
input_tokens1×
cache_creation_input_tokens, 5-minute TTL1.25×
cache_creation_input_tokens, 1-hour TTL2×
cache_read_input_tokens0.1× (0.05× on Opus 5.5 and Sonnet 5.5)

The usage object can also break the writes down by TTL, in a cache_creation field, with ephemeral_5m_input_tokens and ephemeral_1h_input_tokens. Their sum equals cache_creation_input_tokens. Use the breakdown when you mix TTLs, since the two cost different amounts.


Two Useful Measures

MeasureFormulaWhat it tells you
Hit rateread / total inputHow much of the input came from the cache
Effective costThe sum of each field times its priceWhat the request actually cost

A high hit rate on a request that wrote nothing is the ideal pattern. A low hit rate means the prefix changed, or the request was below the minimum, or the entry expired. Lessons 126 and 127 explain how to find out which.


Practical Example: A Cache Report

This module turns usage fields into a cost report. Its main function works on any usage object, so the example runs offline with a fabricated usage record, and the same function works on live responses.

"""cache_report.py: turn usage fields into hit rate and cost, with per-model prices."""

from types import SimpleNamespace

# USD per million tokens, as listed on the pricing page
PRICES = {
    "claude-sonnet-5-5": {"input": 2.00, "write_5m": 2.50, "write_1h": 4.00, "read": 0.10},
    "claude-opus-5-5": {"input": 4.00, "write_5m": 5.00, "write_1h": 8.00, "read": 0.20},
}


def write_breakdown(usage) -> tuple[int, int]:
    """Return (5-minute writes, 1-hour writes), using the TTL breakdown when it is present."""
    detail = getattr(usage, "cache_creation", None)
    if detail is not None:
        return detail.ephemeral_5m_input_tokens, detail.ephemeral_1h_input_tokens
    return usage.cache_creation_input_tokens, 0


def total_input(usage) -> int:
    return usage.input_tokens + usage.cache_creation_input_tokens + usage.cache_read_input_tokens


def hit_rate(usage) -> float:
    total = total_input(usage)
    return usage.cache_read_input_tokens / total if total else 0.0


def cost(model: str, usage) -> float:
    prices = PRICES[model]
    writes_5m, writes_1h = write_breakdown(usage)
    return (usage.input_tokens * prices["input"]
            + writes_5m * prices["write_5m"]
            + writes_1h * prices["write_1h"]
            + usage.cache_read_input_tokens * prices["read"]) / 1_000_000


def cost_without_caching(model: str, usage) -> float:
    return total_input(usage) * PRICES[model]["input"] / 1_000_000


def report(model: str, usage) -> str:
    with_cache = cost(model, usage)
    without = cost_without_caching(model, usage)
    saved = 100 * (1 - with_cache / without) if without else 0.0
    return (f"total input tokens: {total_input(usage)}\n"
            f"hit rate: {100 * hit_rate(usage):.2f}%\n"
            f"cost with caching: ${with_cache:.4f}\n"
            f"cost without caching: ${without:.4f}\n"
            f"saved: {saved:.0f}%")


if __name__ == "__main__":
    # A fabricated usage record: 50 new tokens, 100,000 read from the cache, nothing written
    usage = SimpleNamespace(
        input_tokens=50,
        cache_creation_input_tokens=0,
        cache_read_input_tokens=100_000,
        cache_creation=SimpleNamespace(ephemeral_5m_input_tokens=0, ephemeral_1h_input_tokens=0),
    )
    print(report("claude-sonnet-5-5", usage))

    # On a live response, the same functions work directly:
    # message = client.messages.create(...)
    # print(report("claude-opus-5-5", message.usage))

Output:

total input tokens: 100050
hit rate: 99.95%
cost with caching: $0.0101
cost without caching: $0.2001
saved: 95%

How It Works

  1. total_input() adds all three fields. It is the number to use for context-size checks and rate-limit thinking.
  2. hit_rate() divides the cache reads by the total. The example's 99.95% means almost the whole prompt came from the cache.
  3. write_breakdown() uses the TTL detail when the response includes it, so 5-minute and 1-hour writes are priced separately. It falls back to the total when the detail is absent.
  4. cost() multiplies each field by its own price, using the per-model table. The reads are cheap, and the 50 uncached tokens are charged at the full input rate.
  5. cost_without_caching() prices the same total at the normal input rate, which gives the baseline for the savings.
  6. The numbers check out by hand. The 100,000 read tokens cost 0.1 million times $0.10, which is $0.01. The 50 new tokens cost $0.0001. Together that is $0.0101, against $0.2001 for the full 100,050 tokens at $2.00 per million.

Note: the fabricated record is useful for testing the functions. Live numbers will differ, and the prices in PRICES must be kept current with the pricing page, because prices can change.


Common Mistakes

Reading input_tokens as the Total

What happens: costs and context sizes look far smaller than they really are.

Fix: always add the three fields.

Ignoring Writes

What happens: a request that wrote a large entry looks cheap, because it seems to have read nothing.

Fix: include cache_creation_input_tokens in cost calculations.

Using One Price for All Fields

What happens: writes and reads are costed as if they were normal input, so the report is wrong in both directions.

Fix: price each field separately, using the table.

Hardcoding Prices

What happens: prices change, and reports silently drift from the real bill.

Fix: keep prices in configuration, and review them when the pricing page changes.

Ignoring the Model-Specific Read Multiplier

What happens: the cost of reads on Opus 5.5 is overstated by a factor of two if you assume 0.1×.

Fix: use the per-model read price from the pricing page.


Best Practices

  • Log all three fields for every request.
  • Report hit rate and effective cost separately for each feature.
  • Price writes and reads separately, using current, per-model prices.
  • Track the TTL breakdown when you use both TTLs.
  • Alert on hit rates that drop suddenly, since a drop usually means a prompt change (Lesson 127).

Exercise

  1. Change the usage record so cache_creation_input_tokens is 100,000 and cache_read_input_tokens is 0. What does the report show, and what does that tell you about a first request?
  2. Set ephemeral_1h_input_tokens to 100,000 and ephemeral_5m_input_tokens to 0, with cache_creation_input_tokens also at 100,000. How much does the write cost change?
  3. Compute the hit rate for a request with 40,000 read tokens, 0 written tokens, and 10,000 uncached tokens.
  4. Explain why the function uses getattr(usage, "cache_creation", None) instead of accessing the attribute directly.
Show answers for steps 2 and 4

Step 2: with the 1-hour TTL, the write is priced at $4.00 per million for Sonnet 5.5 instead of $2.50, so the write cost rises from $0.25 to $0.40 for 100,000 tokens. The 5-minute and 1-hour writes are priced differently, which is why the breakdown matters. (In the Opus 5.5 table, the difference is $8.00 against $5.00.)

Step 4: not every response includes the cache_creation breakdown, and older usage records or responses from other providers may not have it. getattr with a default returns None when the attribute is missing, and the function then falls back to the combined total. Accessing the attribute directly would raise an error in that case, and make the report fail on valid responses.

0 Comments

Reviewed before they appear

No comments yet.

Anthropic SDK Using Python
Anthropic SDK Intro Claude API Intro SDK Requirements Install Anthropic SDK Upgrade the SDK Create an API Key Set API Keys Use Environment Variables Import Anthropic Create a Client Send Your First Message Choose a Claude Model Set Max Tokens Read a Response Print Response Text Understand Message Blocks Use System Prompts Send User Messages Set Temperature Handle Stop Reasons Messages API Intro Create Messages Use Message Roles Send Conversation History Continue a Conversation Build a Chat Loop Reset Chat History Use Multiple Turns Inspect Message IDs Read Usage Data Write Clear Prompts Set a System Prompt Add Context to Prompts Use Prompt Templates Add Examples to Prompts Request JSON Output Use XML Tags Control Response Length Improve Prompt Quality Avoid Prompt Injection Read Text Blocks Read Content Blocks Check Stop Reasons Count Input Tokens Count Output Tokens Count Tokens Before Sending Limit Output Tokens Use Stop Sequences Read Request IDs Parse API Responses Streaming Intro Stream Text Output Use Stream Events Use Stream Helpers Get Final Messages Stream Async Responses Handle Stream Errors Build a Live Chat Stream Long Responses Close Streams Safely Build a Chatbot Keep Chat History Manage Chat Sessions Add Chat Memory Build a CLI Chatbot Build a Web Chatbot Use Flask with Claude Use FastAPI with Claude Return JSON from an API Add Chat Error Messages Async SDK Intro Create an Async Client Send Async Messages Use asyncio Run Parallel Requests Stream Async Text Handle Async Errors Set Async Timeouts Close Async Clients Choose Sync or Async Tool Use Intro Define a Tool Create Tool Schemas Send Tools to Claude Read Tool Calls Run Python Tools Return Tool Results Continue After Tool Use Control Tool Choice Handle Tool Errors Use the beta_tool Decorator Create a Tool Runner Add Tool Docstrings Validate Tool Inputs Use Multiple Tools Limit Tool Calls Log Tool Activity Build a Weather Tool Build a Search Tool Build a Calculator Tool Structured Output Intro Request JSON Data Validate JSON Output Use Pydantic Models Handle Invalid JSON Extract Fields from Text Generate Typed Data Use Image URLs Send Base64 Images Read PDFs with Claude Use Output Schemas Build Data Extraction Test Structured Responses Send Image Inputs Upload Files Use File IDs Process Text Files Summarize Documents Extract Table Data Handle File Errors Read Cache Usage Reduce Repeated Input Costs Debug Cache Misses Prompt Caching Intro Enable Prompt Caching Cache System Prompts Cache Long Context Test Cache Performance Use Cache Breakpoints Cache Best Practices Message Batches Intro Create a Message Batch Set Batch IDs Check Batch Status List Message Batches Get Batch Results Process Batch Outputs Handle Failed Batch Items Use Batches for Bulk Tasks Compare Batches and Requests Handle API Errors Handle Rate Limits Retry Failed Requests Set Request Timeouts Handle Network Errors Use Exponential Backoff Build Reliable API Calls Use Extended Thinking Log API Errors Track Request IDs Paginate API Results Set Thinking Budgets Read Thinking Blocks Use Tool Search Use Web Search Tools Use Computer Tools Use Citations Use Token Counting Read Raw Responses Set Custom Headers Use Claude on AWS Bedrock Use Claude on Vertex AI Use Claude on Microsoft Foundry Configure Cloud Credentials Choose a Cloud Provider Compare API Integrations Configure Provider Clients Handle Cloud Errors Use Cloud SDK Extras Deploy Cloud Apps Protect API Keys Use a .env File Ignore Secrets in Git Validate User Input Reduce Prompt Injection Limit Tool Permissions Avoid Unsafe Tool Calls Sanitize Tool Results Set Usage Limits Review Security Logs Test SDK Functions Mock API Responses Test Error Handling Test Streaming Code Measure Token Usage Track API Costs Configure Logging Deploy with Docker Deploy a FastAPI App Monitor Production Apps Build a Claude Chatbot Build a Text Summarizer Build a Code Explainer Build a JSON Extractor Build a PDF Assistant Build a Streaming Chat App Build a Tool-Calling Agent Build a Batch Processor Build a Research Assistant Build a Claude API Dashboard
Ask about this post
AI Ask about this post

Ask questions about Read Cache Usage and get answers drawn from it.

Signed-in readers only.