Study guides / CCDV-F / Domain 2

Model Selection & Optimisation · Lesson 2 of 5

2.2 - Token Economics: Counting, Budgeting and Pricing

Reason about cost per completed task in tokens, not requests - and know exactly which of the four token buckets each lever actually moves.

Claude is priced per token, and a request's cost is never one number — it's the sum of up to four separate token buckets, each billed at its own rate relative to the model's base input price: regular (uncached) input at the full input rate, cache writes at a premium over the input rate, cache reads at a steep discount off the input rate, and output at the (much higher) output rate. The exam-relevant mental model is: cost is optimised per completed task, not per token and not per request — a cheaper-looking call that fails and needs a retry can cost more overall than a pricier call that succeeds once.

The four token buckets

Every response's usage object can report all four:

Why output costs more, and why max_tokens isn't a cost lever

Because output is priced several times higher than input, trimming a rambling response is usually a bigger lever than trimming a similarly-sized block of input — a tighter prompt ("answer in 2–3 sentences") or an explicit output-shape example does more than shaving a few hundred tokens off a system prompt. It's tempting to reach for max_tokens as the control here, but it isn't one: max_tokens is an enforced ceiling the model is never told about. Hitting it truncates the response mid-thought (stop_reason: "max_tokens") — that's a failed attempt, not a cost saving, and in agentic workloads a capped run still spends tokens without producing a usable result, so cost per completed task doesn't actually improve.

Common exam distractor

An answer that frames cost purely in terms of "number of requests" is incomplete. Two requests with a bloated context and a rambling response can cost far more than twenty lean ones — token volume across all four buckets, not call count, is what's billed.

Estimating and verifying spend

Two tools make token cost concrete instead of guessed. The token counting endpoint (messages.count_tokens) returns an exact input token count for a given system/messages/tools payload without running inference — useful as a pre-flight gate on unbounded user input, or to sanity-check a prompt change before it goes live. And every response's usage object is the ground truth after the fact: if cache_read_input_tokens stays at zero across repeated, near-identical requests, something is silently breaking the cache (a timestamp interpolated into the system prompt, unsorted JSON, a varying tool list) and the caching lever from Lesson 2.5 isn't actually engaged, regardless of what the code appears to do.

Non-text input has its own token cost

Token economics isn't only about prose. Images are tokenized by pixel area, roughly one token per 28×28-pixel patch — so cost scales with resolution, not with how much visually "matters" in the image. Sending a full-resolution screenshot when the task only needs to read a small label wastes input tokens for no accuracy benefit; downscaling to a size like 1280×720 caps a single image near roughly 1,200 tokens and is a genuine, quality-neutral cost lever, distinct from anything covered by caching or batching. A large PDF processed as a document input adds up similarly — per-page token cost, not per-byte — which matters when estimating the cost of a document-heavy workload before it ships.

Organisation-level spend vs. per-call spend

Everything above computes cost from a single response's usage object, which is the right level for optimising one call. For total spend across an application or an org, the Usage and Cost Admin API reports the same four buckets in aggregate — grouped by model, API key, or workspace — without spending any tokens to read, since report reads aren't inference calls. The exam-relevant distinction: per-call usage tells you whether one change helped; the Admin usage/cost reports tell you whether that change actually moved the bill, and they're the tool for confirming a cost optimisation after it ships, not just estimating one beforehand.

Key concept

The Batches API discounts every token type — regular input, cache writes, cache reads, and output — by 50%, on top of whatever caching already saved. It only applies to latency-insensitive, asynchronous work (Lesson 2.5).

Exam traps

Practice question

A team wants to cut their Claude API bill for a chat feature without hurting response quality. Which change is the most direct lever on cost?

  • A Batch multiple unrelated user conversations into a single request to reduce call count.

    Combining unrelated conversations into one call doesn't reduce total token volume and complicates response separation - it doesn't meaningfully cut cost.

  • B Trim accumulated but no-longer-needed conversation history and cache the stable system prompt/tool definitions. Correct

    Both directly reduce the token volume actually billed on each call - the two most effective levers described in the lesson.

  • C Increase max_tokens so Claude never truncates mid-answer.

    Raising the output cap doesn't reduce cost - if anything it removes a ceiling that was limiting output-token spend.

  • D Switch every call to streaming mode.

    Streaming changes how output is delivered, not how many tokens are billed - it has no direct effect on cost.

Build exercise: Estimate, cache, and reduce a real request's token cost across all four buckets

Beginner · 30 minutes

You'll practice:

  1. Take a real or sample multi-turn conversation history, send it as-is, and print the full response.usage object.

    You need a real baseline across all four buckets before you can meaningfully judge whether a change helped.

    You should see: input_tokens and output_tokens populated; cache_creation_input_tokens and cache_read_input_tokens at zero or None since nothing has been cached yet.

    Hints
    1. What method on the messages resource lets you send a full conversation history in one call?
    2. Build a messages list with alternating user/assistant turns, pass it to client.messages.create, then print response.usage directly - it's a structured object with all four token fields.
    3. import anthropic
      client = anthropic.Anthropic()
      
      messages = [
          {"role": "user", "content": "I need help tracking order #48213."},
          {"role": "assistant", "content": "I can help. Let me check the status..."},
          {"role": "user", "content": "It's been 5 days, still no movement."},
      ]
      
      response = client.messages.create(
          model="claude-sonnet-5",
          max_tokens=1024,
          messages=messages,
      )
      print(response.usage)
  2. Before resending, call client.messages.count_tokens with the same system/messages payload and compare the returned input_tokens to what the real call reported.

    Token counting lets you estimate cost pre-flight - the exam distinguishes this from guessing character counts or word counts, which don't map cleanly to tokens.

    You should see: A token count close to (or matching) the input_tokens value from step 1's real response.

    Hints
    1. Is there an endpoint that returns a token count without running inference?
    2. count_tokens takes the same model/system/messages/tools shape as messages.create but returns only a count - no generation happens, so it's cheap to call.
    3. count = client.messages.count_tokens(
          model="claude-sonnet-5",
          messages=messages,
      )
      print("Estimated input tokens:", count.input_tokens)
  3. Add a cache_control breakpoint to a large, stable piece of system content (e.g. a policy document), send the request twice in a row, and compare cache_read_input_tokens between the first and second call.

    This makes the cost impact of caching concrete: the first call pays the cache-write premium, the second call should show a large cache_read_input_tokens value billed at a fraction of the input rate.

    You should see: The first response shows cache_creation_input_tokens > 0 and cache_read_input_tokens at 0; the second shows the reverse.

    Hints
    1. Where does cache_control go, and what happens on the very first call versus every call after it?
    2. Mark the stable system block with cache_control: {type: 'ephemeral'}. The first call writes the cache (billed at a premium); an identical second call within the TTL reads it back at a steep discount.
    3. system_prompt = [{
          "type": "text",
          "text": LONG_POLICY_DOCUMENT,
          "cache_control": {"type": "ephemeral"},
      }]
      
      r1 = client.messages.create(model="claude-sonnet-5", max_tokens=512, system=system_prompt, messages=messages)
      r2 = client.messages.create(model="claude-sonnet-5", max_tokens=512, system=system_prompt, messages=messages)
      
      print("First call cache_read:", r1.usage.cache_read_input_tokens)
      print("Second call cache_read:", r2.usage.cache_read_input_tokens)  # should be > 0
  4. Write a helper that computes total dollar cost from a usage object across all four buckets (regular input, cache write at 1.25x, cache read at 0.1x, output), and run it against the second call from step 3.

    This is the actual exam-relevant skill - treating cost as the sum of four separately-priced buckets rather than a single input/output split.

    You should see: A dollar figure for the cached call that is noticeably lower than the same call would cost with cache_read billed at the full input rate.

    Hints
    1. You have four token counts and effectively three distinct rates (input, a cache-write multiplier on input, a cache-read multiplier on input, and output) - how do you combine them?
    2. Multiply each bucket's token count by its own effective rate (regular input at 1x, cache write at 1.25x input, cache read at 0.1x input, output at the output rate) and sum.
    3. def estimate_cost(usage, input_rate=2.00, output_rate=10.00):
          regular_in = usage.input_tokens / 1_000_000 * input_rate
          cache_write = (usage.cache_creation_input_tokens or 0) / 1_000_000 * input_rate * 1.25
          cache_read = (usage.cache_read_input_tokens or 0) / 1_000_000 * input_rate * 0.1
          out = usage.output_tokens / 1_000_000 * output_rate
          return regular_in + cache_write + cache_read + out
      
      print(f"${estimate_cost(r2.usage):.6f}")
  5. Trim any clearly stale content from the conversation history (e.g. an old tool result no longer relevant) and constrain the response with an explicit output-shape instruction, then re-send and compare output_tokens to the baseline.

    This isolates the two highest-leverage, quality-neutral levers from the lesson: input hygiene and shortening the (more expensive) output, rather than reaching for max_tokens.

    You should see: A measurable drop in both input_tokens (from trimming) and output_tokens (from the concision instruction), with no loss of answer quality.

    Hints
    1. Which is the bigger lever here - cutting input tokens or cutting output tokens, given their relative price?
    2. Filter out any message content that's no longer needed for the answer, and add a system instruction constraining response length/format rather than relying on max_tokens to cut it off.
    3. trimmed_messages = [m for m in messages if "shipped" not in str(m.get("content", ""))]
      
      response = client.messages.create(
          model="claude-sonnet-5",
          max_tokens=300,
          system="Answer in 2-3 sentences, no preamble.",
          messages=trimmed_messages,
      )
      print("Output tokens:", response.usage.output_tokens)

Sources