DevAssist was a coding assistant startup. They launched their beta on a Tuesday and got featured on Hacker News. By midnight, 3,000 developers had signed up. By 2 AM, their API calls started failing with 429 (rate limit) errors. By 3 AM, their Anthropic account hit its spending limit and every request returned an error.
The founder, Elena, woke up to a Slack channel full of angry beta users and a $4,200 API bill for a single night. What went wrong?
Three things: (1) Elena hadn't configured spending limits, so costs spiraled unchecked. (2) She didn't implement rate limiting on her own API, so every user keystroke fired a new Claude request. (3) She used Opus for everything, including syntax highlighting suggestions that Haiku could handle at 1/20th the cost.
After that night, Elena built three systems: a cost estimation calculator, per-user rate limiting, and a model routing layer. Her next month cost $800 instead of $4,200 -- serving 5x more users.
This lesson teaches you everything Elena learned the hard way.
Concept: How Claude Pricing Works
Token-Based Pricing
Claude charges per token -- the fundamental unit of text processing. A token is roughly:
4 characters in English
0.75 words (or about 1.3 tokens per word)
A single code keyword (like function or const) is typically one token
Numbers can be multiple tokens: 1000 is one token, 123456789 might be three
Output tokens: Everything Claude generates in response
Total Cost = (input_tokens x input_price) + (output_tokens x output_price)
Current Pricing Table
Model
Input (per 1M tokens)
Output (per 1M tokens)
With Prompt Caching (Input)
Claude Opus 4
$15.00
$75.00
$1.50 (cached)
Claude Sonnet 4
$3.00
$15.00
$0.30 (cached)
Claude Haiku 3.5
$0.80
$4.00
$0.08 (cached)
5x
Output vs Input Cost
Output tokens cost 5x more than input tokens across all models -- controlling response length is the single easiest cost optimization
Key observations:
Output tokens cost 5x more than input tokens across all models
Opus is 5x more expensive than Sonnet, which is ~4x more expensive than Haiku
Prompt caching reduces input costs by 90% for repeated system prompts
Understanding Token Counts
Before you can estimate costs, you need to understand where tokens go in a typical request:
Python
import anthropicclient = anthropic.Anthropic()# This request shows where tokens are spentresponse = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1024, system="You are a helpful coding assistant.", # ~7 tokens messages=[ { "role": "user", "content": "Write a Python function to reverse a string." # ~10 tokens } ],)# Check actual token usageprint(f"Input tokens: {response.usage.input_tokens}")print(f"Output tokens: {response.usage.output_tokens}")# Calculate cost (Sonnet pricing)input_cost = (response.usage.input_tokens / 1_000_000) * 3.00output_cost = (response.usage.output_tokens / 1_000_000) * 15.00total_cost = input_cost + output_costprint(f"Total cost: ${total_cost:.6f}")
typescript
import Anthropic from "@anthropic-ai/sdk";const client = new Anthropic();const response = await client.messages.create({ model: "claude-sonnet-4-20250514", max_tokens: 1024, system: "You are a helpful coding assistant.", messages: [{ role: "user", content: "Write a Python function to reverse a string." }]});console.log(`Input tokens: ${response.usage.input_tokens}`);console.log(`Output tokens: ${response.usage.output_tokens}`);const inputCost = (response.usage.input_tokens / 1_000_000) * 3.00;const outputCost = (response.usage.output_tokens / 1_000_000) * 15.00;console.log(`Total cost: $${(inputCost + outputCost).toFixed(6)}`);
The Hidden Token Costs
Many developers underestimate their token usage because they forget about several hidden costs:
1. System prompts count as input tokens
A 500-word system prompt adds ~650 tokens to every single request. At 10,000 requests/day with Sonnet, that's 6.5M tokens/day x $3/M = $19.50/day just for the system prompt.
Tip
Use Pricing & Rate Limits in a low-risk branch or scratch project first. That keeps the lesson concrete without making your first attempt carry production pressure.
2. Conversation history accumulates
In a multi-turn conversation, you resend the entire history with each request. A 20-message conversation might have 10,000 tokens of history -- all charged as input.
3. Tool definitions count as input tokens
Each tool definition (JSON Schema) adds tokens. Five tool definitions might add 500-1000 input tokens per request.
4. Images are expensive
Images are tokenized at approximately 1,600 tokens per 1 megapixel. A standard 1080p screenshot costs about 1,300 tokens.
Python
def estimate_conversation_cost( model: str, system_prompt_tokens: int, avg_user_message_tokens: int, avg_assistant_response_tokens: int, num_turns: int, tool_definition_tokens: int = 0) -> dict: """Estimate the total cost of a multi-turn conversation.""" pricing = { "claude-haiku-3-5-20241022": {"input": 0.80, "output": 4.00}, "claude-sonnet-4-20250514": {"input": 3.00, "output": 15.00}, "claude-opus-4-20250514": {"input": 15.00, "output": 75.00}, } prices = pricing[model] total_input_tokens = 0 total_output_tokens = 0 for turn in range(1, num_turns + 1): # Each turn sends: system + tools + all history + new message history_tokens = (turn - 1) * ( avg_user_message_tokens + avg_assistant_response_tokens ) turn_input = ( system_prompt_tokens + tool_definition_tokens + history_tokens + avg_user_message_tokens ) total_input_tokens += turn_input total_output_tokens += avg_assistant_response_tokens input_cost = (total_input_tokens / 1_000_000) * prices["input"] output_cost = (total_output_tokens / 1_000_000) * prices["output"] return { "total_input_tokens": total_input_tokens, "total_output_tokens": total_output_tokens, "input_cost": round(input_cost, 4), "output_cost": round(output_cost, 4), "total_cost": round(input_cost + output_cost, 4), "cost_per_turn": round( (input_cost + output_cost) / num_turns, 4 ), }# Example: 10-turn customer support conversation on Sonnetresult = estimate_conversation_cost( model="claude-sonnet-4-20250514", system_prompt_tokens=500, avg_user_message_tokens=100, avg_assistant_response_tokens=300, num_turns=10, tool_definition_tokens=200)print(f"Total input tokens: {result['total_input_tokens']:,}")print(f"Total output tokens: {result['total_output_tokens']:,}")print(f"Total cost: ${result['total_cost']}")print(f"Cost per turn: ${result['cost_per_turn']}")# Notice how later turns cost much more due to history accumulation
Rate Limits
Anthropic enforces rate limits to ensure fair access and system stability. Rate limits operate on three dimensions:
Dimension
What It Limits
Requests per minute (RPM)
Number of API calls
Input tokens per minute (ITPM)
Total input tokens across all requests
Output tokens per minute (OTPM)
Total output tokens across all requests
Your effective rate limit is the most restrictive of these three. If your RPM is 1,000 but your ITPM allows only enough for 500 requests, you're effectively limited to 500.
Rate limit tiers scale with your usage and spending:
Measure the Pricing & Rate Limits Tradeoff
Choose one task you repeat often.
Run it with the model, cost, or performance setting discussed in this lesson.
Record latency, quality, and cost so you can choose intentionally next time.
Tier
Usage Requirement
RPM
ITPM
OTPM
Tier 1 (Free trial)
$0
50
40,000
8,000
Tier 2
$40+ credited
1,000
80,000
16,000
Tier 3
$200+ credited
2,000
160,000
32,000
Tier 4
$400+ credited
4,000
400,000
80,000
These are approximate values and may vary. Check console.anthropic.com for current limits.
Handling Rate Limits in Code
When you hit a rate limit, the API returns a 429 status code with a retry-after header. Here's how to handle it properly:
Python
import anthropicimport timeimport randomclient = anthropic.Anthropic()def call_with_backoff( messages: list, model: str = "claude-sonnet-4-20250514", max_retries: int = 5, max_tokens: int = 1024) -> anthropic.types.Message: """Make an API call with exponential backoff for rate limits.""" for attempt in range(max_retries): try: return client.messages.create( model=model, max_tokens=max_tokens, messages=messages ) except anthropic.RateLimitError as e: if attempt == max_retries - 1: raise # Give up after max retries # Exponential backoff with jitter base_delay = 2 ** attempt # 1, 2, 4, 8, 16 seconds jitter = random.uniform(0, base_delay * 0.5) delay = base_delay + jitter print(f"Rate limited. Waiting {delay:.1f}s " f"(attempt {attempt + 1}/{max_retries})") time.sleep(delay) except anthropic.APIStatusError as e: if e.status_code == 529: # Overloaded time.sleep(5) continue raise# Usageresponse = call_with_backoff( messages=[{"role": "user", "content": "Hello!"}])
Prompt caching is one of the most impactful cost optimization techniques. When you mark parts of your prompt as cacheable, Anthropic stores them server-side. Subsequent requests that include the same cached content pay only 10% of the normal input price.
What can be cached:
System prompts
Tool definitions
Large reference documents
Few-shot examples
Minimum cacheable size: 1,024 tokens (Sonnet/Opus) or 2,048 tokens (Haiku)
Python
import anthropicclient = anthropic.Anthropic()# Using prompt caching with a large system promptlarge_system_prompt = """You are an expert legal analyst.Here is the complete regulatory framework you must reference:[imagine 2000+ tokens of legal reference material here]When analyzing contracts, always check against sections4.2, 7.1, and 12.5 of the framework above."""response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1024, system=[ { "type": "text", "text": large_system_prompt, "cache_control": {"type": "ephemeral"} } ], messages=[{ "role": "user", "content": "Analyze this clause: ..." }])# Check cache performanceprint(f"Cache creation tokens: " f"{response.usage.cache_creation_input_tokens}")print(f"Cache read tokens: " f"{response.usage.cache_read_input_tokens}")# First call: cache_creation > 0, cache_read = 0# Subsequent calls: cache_creation = 0, cache_read > 0
typescript
import Anthropic from "@anthropic-ai/sdk";const client = new Anthropic();const largeSystemPrompt = `You are an expert legal analyst.Here is the complete regulatory framework you must reference:[imagine 2000+ tokens of legal reference material here]When analyzing contracts, always check against sections4.2, 7.1, and 12.5 of the framework above.`;const response = await client.messages.create({ model: "claude-sonnet-4-20250514", max_tokens: 1024, system: [ { type: "text", text: largeSystemPrompt, cache_control: { type: "ephemeral" }, } ], messages: [{ role: "user", content: "Analyze this clause: ..." }]});console.log(`Cache creation: ${response.usage.cache_creation_input_tokens}`);console.log(`Cache read: ${response.usage.cache_read_input_tokens}`);
Batch API: The 50% Discount
For non-time-sensitive workloads, the Batch API offers a 50% discount. You submit up to 10,000 requests at once, and results are delivered within 24 hours.
Quick Check
What is the main benefit of using Pricing & Rate Limits well in Claude Code?
Good for: Data processing, content generation, evaluation runs, classification at scale.
Not good for: Real-time chat, interactive applications, anything user-facing.
Apply: Building a Cost Dashboard
Here's a practical cost tracking system you can integrate into any application:
Python
import anthropicimport jsonfrom datetime import datetime, timezonefrom dataclasses import dataclass, field, asdictfrom typing import Optional@dataclassclass APICallRecord: timestamp: str model: str task: str input_tokens: int output_tokens: int cache_read_tokens: int cache_creation_tokens: int cost_usd: float latency_ms: floatclass CostTracker: """Track and analyze Claude API costs.""" PRICING = { "claude-haiku-3-5-20241022": { "input": 0.80, "output": 4.00, "cache_read": 0.08, "cache_creation": 1.00, }, "claude-sonnet-4-20250514": { "input": 3.00, "output": 15.00, "cache_read": 0.30, "cache_creation": 3.75, }, "claude-opus-4-20250514": { "input": 15.00, "output": 75.00, "cache_read": 1.50, "cache_creation": 18.75, }, } def __init__(self): self.records: list[APICallRecord] = [] def calculate_cost(self, model: str, usage: anthropic.types.Usage) -> float: """Calculate the cost of a single API call.""" prices = self.PRICING.get(model, self.PRICING[ "claude-sonnet-4-20250514" ]) input_tokens = usage.input_tokens output_tokens = usage.output_tokens cache_read = getattr( usage, 'cache_read_input_tokens', 0 ) or 0 cache_creation = getattr( usage, 'cache_creation_input_tokens', 0 ) or 0 # Subtract cached tokens from regular input regular_input = input_tokens - cache_read - cache_creation cost = ( (regular_input / 1_000_000) * prices["input"] + (output_tokens / 1_000_000) * prices["output"] + (cache_read / 1_000_000) * prices["cache_read"] + (cache_creation / 1_000_000) * prices["cache_creation"] ) return round(cost, 6) def record(self, model: str, task: str, response: anthropic.types.Message, latency_ms: float): """Record an API call for tracking.""" usage = response.usage cost = self.calculate_cost(model, usage) record = APICallRecord( timestamp=datetime.now(timezone.utc).isoformat(), model=model, task=task, input_tokens=usage.input_tokens, output_tokens=usage.output_tokens, cache_read_tokens=getattr( usage, 'cache_read_input_tokens', 0 ) or 0, cache_creation_tokens=getattr( usage, 'cache_creation_input_tokens', 0 ) or 0, cost_usd=cost, latency_ms=latency_ms, ) self.records.append(record) return record def summary(self) -> dict: """Get a cost summary.""" if not self.records: return {"total_cost": 0, "total_calls": 0} total_cost = sum(r.cost_usd for r in self.records) by_model = {} by_task = {} for r in self.records: by_model.setdefault(r.model, 0.0) by_model[r.model] += r.cost_usd by_task.setdefault(r.task, 0.0) by_task[r.task] += r.cost_usd return { "total_cost": round(total_cost, 4), "total_calls": len(self.records), "cost_by_model": { k: round(v, 4) for k, v in by_model.items() }, "cost_by_task": { k: round(v, 4) for k, v in by_task.items() }, "avg_cost_per_call": round( total_cost / len(self.records), 6 ), } def alert_if_over_budget( self, daily_budget: float ) -> Optional[str]: """Check if spending exceeds daily budget.""" today = datetime.now(timezone.utc).date().isoformat() today_cost = sum( r.cost_usd for r in self.records if r.timestamp.startswith(today) ) if today_cost > daily_budget: return ( f"ALERT: Daily spend ${today_cost:.2f} " f"exceeds budget ${daily_budget:.2f}" ) elif today_cost > daily_budget * 0.8: return ( f"WARNING: Daily spend ${today_cost:.2f} " f"is at {today_cost/daily_budget*100:.0f}% " f"of budget" ) return None# Usageimport timetracker = CostTracker()client = anthropic.Anthropic()start = time.time()response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1024, messages=[{"role": "user", "content": "Hello!"}])latency = (time.time() - start) * 1000record = tracker.record( "claude-sonnet-4-20250514", "greeting", response, latency)print(f"Cost: ${record.cost_usd}")print(json.dumps(tracker.summary(), indent=2))
API Cost Management
Do
Build cost tracking into your application from day one and set spending limits before writing any code
Don't
Treat cost monitoring as an afterthought -- by the time you notice runaway spending, the bill is already large
Try This Now
Integrate the CostTracker into an existing project and monitor costs for a week.
Add a save_to_file() method that persists records to JSON for later analysis.
Build a function that projects monthly costs based on the current day's spending rate.
Add per-user tracking to identify your most expensive users.
Reflect: Managing Your API Budget
Tip
Set spending limits immediately. In the Anthropic Console, configure monthly spending limits before you write any code. It's the simplest safeguard against runaway costs, and it takes 30 seconds.
Warning
Output tokens are 5x more expensive than input tokens. The single most impactful cost optimization is controlling output length. Add explicit instructions like "Respond in under 200 words" or "Return only the JSON object, no explanation." This alone can cut costs 30-50%.
Cost Optimization Checklist
Strategy
Savings
Effort
Use the right model per task
50-80%
Medium
Enable prompt caching
Up to 90% on input
Low
Control output length
30-50%
Low
Trim conversation history
20-40%
Medium
Use Batch API for async work
50%
Low
Implement request deduplication
Varies
Medium
The Spending Guardrails Every App Needs
Console-level spending limit -- hard cap in the Anthropic dashboard
Application-level daily budget -- your code stops calling the API when you hit the limit
Per-user rate limiting -- prevent any single user from consuming disproportionate resources
Cost alerting -- get notified when spending is trending above projections
Model fallback -- if budget is tight, automatically downgrade to cheaper models
Key Takeaways
Claude charges per token (~4 characters) with separate input and output prices
Output tokens cost 5x more than input tokens -- controlling response length is the easiest cost win
Prompt caching reduces repeated input costs by 90% -- use it for system prompts and tool definitions
Rate limits operate on three dimensions: requests/minute, input tokens/minute, and output tokens/minute
Always implement exponential backoff with jitter for rate limit handling
Hidden token costs include conversation history (accumulates per turn), system prompts, tool definitions, and images
The Batch API offers 50% savings for non-real-time workloads
Build cost tracking into your application from day one -- not as an afterthought
Set spending limits in the Anthropic Console before writing any code
Multi-turn conversations get exponentially more expensive as history grows -- implement conversation trimming
Next chapter: You'll make your first API call to Claude -- from setting up your API key to sending requests and handling responses.