# LLM prompt caching: check the usage before the bill

> Markdown mirror of https://router.one/llm-prompt-caching for AI assistants and crawlers. Router One is an OpenAI-compatible LLM API gateway.
> Last updated: 2026-09-05

A cache counter describes reported token usage. It does not, by itself, establish a hit rate, a saving, or an amount charged. Start with the request's protocol and complete usage object, separate ordinary input from recorded writes and reads, then compare that request with its cost record. This guide explains that reconciliation; it is not a setup guide for an answer cache.

## Identify the usage format first

The same word, input, can mean different things in different response formats. Read the fields from one completed request; do not combine a raw response, a dashboard summary, and a monthly total as if they were independent token buckets.

| Source | Fields to inspect | Counting rule |
| --- | --- | --- |
| OpenAI-style Chat Completions | prompt_tokens; prompt_tokens_details.cached_tokens | The nested cached count is a subset of prompt_tokens. Do not add it to the prompt total. |
| OpenAI-style Responses | input_tokens; input_tokens_details.cached_tokens; optional input_tokens_details.cache_write_tokens | Both nested counters are already included in input_tokens. Ordinary input equals full input minus the reported read and write portions. Preserve the complete usage object; a missing write field alone does not establish zero written input. |
| Native Anthropic Messages | input_tokens; cache_creation_input_tokens; cache_read_input_tokens | These are separate input categories: ordinary input, newly written input, and read input. Their sum is the full request input. |
| Normalized logs or SDK summaries | inputTokens; cacheCreationInputTokens; cacheReadInputTokens; cachedTokens | Verify the summary's field definitions first. cachedTokens and cacheReadInputTokens may represent the same recorded count, not two additional quantities. |

## Example 1: a nested count is already included

Hypothetical OpenAI-style usage, for arithmetic only—not a real request or bill. This example explicitly assumes zero written input. Of 10,000 input tokens, 6,000 are reported in the nested category and 4,000 remain ordinary input under that assumption. With 500 output tokens, total usage is 10,500. Adding 6,000 to prompt_tokens again would incorrectly report 16,000 input tokens.

`illustrative-usage.json`

```json
{
  "prompt_tokens": 10000,
  "prompt_tokens_details": {
    "cached_tokens": 6000
  },
  "completion_tokens": 500,
  "total_tokens": 10500
}
```

## Example 2: Responses includes both nested portions

Hypothetical Responses usage, for arithmetic only—not a real request or bill. The full input of 10,000 already includes 6,000 reported reads and 2,500 reported writes. Ordinary input is 10,000 − 6,000 − 2,500 = 1,500 tokens. The three non-overlapping portions sum to 10,000; with 500 output tokens, total usage remains 10,500. Neither nested portion should be added to input_tokens again.

`illustrative-usage.json`

```json
{
  "input_tokens": 10000,
  "input_tokens_details": {
    "cached_tokens": 6000,
    "cache_write_tokens": 2500
  },
  "output_tokens": 500,
  "total_tokens": 10500
}
```

## Example 3: separate categories must be summed once

Hypothetical native Anthropic usage, for arithmetic only—not a real request or bill. Full input is 2,000 + 3,000 + 5,000 = 10,000 tokens; adding 500 output tokens gives 10,500. Here input_tokens is the ordinary-input portion, unlike the inclusive input total in the first example.

`illustrative-usage.json`

```json
{
  "input_tokens": 2000,
  "cache_creation_input_tokens": 3000,
  "cache_read_input_tokens": 5000,
  "output_tokens": 500
}
```

## Why normalized statistics must not be added again

A reporting layer may already combine the three input categories into a full-input total while retaining its component counters for inspection. In that case, full input plus the write and read counters counts some tokens twice. Conversely, an ordinary-input-only field still needs those separate categories to reconstruct full input. A camelCase name or a dashboard label alone cannot distinguish these cases.

- Keep the raw protocol fields and derived totals in separate columns.
- Treat cachedTokens and cacheReadInputTokens as possible aliases; do not sum them without proof that they measure different usage.
- Check output separately. A component breakdown explains a total; it is not extra usage to append to that total.

## Reconcile one request in four steps

- Match the request ID, exact model ID, time, and status. Each client or application retry starts a new request; internal provider fallback attempts may remain within the same request. Compare like with like before aggregating a session.
- Capture the complete response usage object in your client. For streaming calls, use the final reported usage when available; an early event or a partial stream can be incomplete. Keep API keys and message bodies out of a billing support example.
- Open Dashboard → Logs and locate that request. The detail view shows input, output, cost and status; it shows the recorded read count when positive. The compact view does not display every raw response field, so an absent row is not proof of zero usage.
- Check the applicable model tariff and input-length tier. The live model page describes current rates; it cannot establish what applied to an older request. If counts or charges still disagree, provide the request ID, time, model and the minimal usage fragment for review rather than assuming the current catalog reproduces an old bill.

## Separate token arithmetic from money

First establish the non-overlapping input categories, then multiply each count by its applicable rate and calculate output separately. Do not apply a single percentage to all of them. A request's token-cost calculation also differs from a final wallet debit when plan coverage or other billing terms apply.

- A nonzero recorded read count does not establish a universal saving. Rates for each category can differ by model and request conditions; use the model detail page and the request record.
- For a model with input-length tiers, full request input includes ordinary input plus distinct written and read input. Crossing the threshold selects the tier for the whole request, including its output; it is not a surcharge on only the excess tokens.
- The cost calculator estimates a workload under its stated assumptions. It does not predict future cache behavior. The pricing methodology explains catalog rates and tier boundaries; per-request records explain a specific call.

## FAQ

### Should I add cached_tokens to prompt_tokens?

Not when cached_tokens is nested under prompt_tokens_details in an OpenAI-style response: it is already part of prompt_tokens. Native Anthropic Messages uses a different shape: ordinary input_tokens, cache_creation_input_tokens and cache_read_input_tokens are separate categories. Identify the format before adding anything.

### How do I account for Responses cache_write_tokens?

In an OpenAI-style Responses usage object, input_tokens_details.cached_tokens and the optional input_tokens_details.cache_write_tokens are both included in input_tokens. Ordinary input is the full input minus both reported portions. Do not subtract only the read count when writes are also reported, or add either portion to the full input again. A missing write field alone does not prove zero written input; confirm the complete usage format before calculating.

### Does a positive cache counter prove a discount or the amount charged?

No. It establishes a reported usage category, not a universal price or saving. Reconcile that category with the applicable model tariff, request conditions, and the request's billing record. Do not infer a fixed percentage or future hit rate from one counter.

### Can I add cachedTokens and cacheReadInputTokens?

Not necessarily. A normalized response can expose the same read count under both names. Do not sum the two; inspect the raw usage format and the summary's definitions. Also confirm whether its input total already includes that count.

### Does recorded read input count toward a long-context tier?

Tier selection uses the full request input, including ordinary input and distinct written and read input. For an inclusive input total, do not add its breakdown again. A threshold is crossed only when that full total is strictly greater than the stated boundary, and the selected tier applies to the whole request.

### Does this guide promise caching support for every model?

No. This is a guide to interpreting reported usage and checking a bill. It does not establish request-parameter support, shared cached answers, a retention duration, cache availability, a hit rate, or a fixed saving. Consult the protocol documentation and the actual response for the model you use.

## See also

- Pricing methodology and input-length tiers: https://router.one/pricing-methodology
- LLM cost calculator: https://router.one/llm-cost-calculator
- Per-request cost tracking: https://router.one/llm-cost-tracking
- Claude Code token costs: https://router.one/blog/claude-code-token-costs-explained
- Application-level cost reduction: https://router.one/blog/reduce-llm-api-costs
- OpenAI: prompt caching usage fields: https://developers.openai.com/api/docs/guides/prompt-caching
- Anthropic: prompt caching usage fields: https://platform.claude.com/docs/en/build-with-claude/prompt-caching
- Canonical page: https://router.one/llm-prompt-caching
- Models and per-model token rates: https://router.one/models (markdown: https://router.one/models.md)
- Pricing: https://router.one/pricing
- API docs (markdown): https://router.one/docs.md
- Company facts: https://router.one/facts/company.md
