Anthropic Load & Scale
Overview
Capacity planning and load testing for Claude API integrations. Key constraint: your rate limits (RPM/ITPM/OTPM) are the ceiling, not your infrastructure.
Capacity Planning
# Calculate required tier based on traffic
def plan_capacity(
requests_per_minute: int,
avg_input_tokens: int,
avg_output_tokens: int,
model: str = "claude-sonnet-4-20250514"
) -> dict:
itpm = requests_per_minute * avg_input_tokens
otpm = requests_per_minute * avg_output_tokens
# Estimate monthly cost
pricing = {
"claude-haiku-4-20250514": (0.80, 4.00),
"claude-sonnet-4-20250514": (3.00, 15.00),
"claude-opus-4-20250514": (15.00, 75.00),
}
rates = pricing[model]
cost_per_request = (avg_input_tokens * rates[0] + avg_output_tokens * rates[1]) / 1_000_000
monthly_cost = cost_per_request * requests_per_minute * 60 * 24 * 30
return {
"rpm_needed": requests_per_minute,
"itpm_needed": itpm,
"otpm_needed": otpm,
"cost_per_request": f"${cost_per_request:.4f}",
"monthly_estimate": f"${monthly_cost:,.0f}",
"recommendation": "Contact Anthropic sales for Scale tier" if requests_per_minute > 500 else "Self-serve tiers sufficient",
}
print(plan_capacity(100, 500, 200))
Load Testing Script
import anthropic
import asyncio
import time
from dataclasses import dataclass
@dataclass
class LoadTestResult:
total_requests: int = 0
successful: int = 0
failed: int = 0
rate_limited: int = 0
avg_latency_ms: float = 0
p99_latency_ms: float = 0
total_input_tokens: int = 0
total_output_tokens: int = 0
async def load_test(
concurrency: int = 10,
total_requests: int = 100,
model: str = "claude-haiku-4-20250514"
) -> LoadTestResult:
client = anthropic.Anthropic()
result = LoadTestResult()
latencies = []
semaphore = asyncio.Semaphore(concurrency)
async def single_request():
async with semaphore:
start = time.monotonic()
try:
msg = client.messages.create(
model=model,
max_tokens=64,
messages=[{"role": "user", "content": "Respond with exactly: OK"}]
)
duration = (time.monotonic() - start) * 1000
latencies.append(duration)
result.successful += 1
result.total_input_tokens += msg.usage.input_tokens
result.total_output_tokens += msg.usage.output_tokens
except anthropic.RateLimitError:
result.rate_limited += 1
except Exception:
result.failed += 1
result.total_requests += 1
tasks = [single_request() for _ in range(total_requests)]
await asyncio.gather(*tasks)
if latencies:
latencies.sort()
result.avg_latency_ms = sum(latencies) / len(latencies)
result.p99_latency_ms = latencies[int(len(latencies) * 0.99)]
return result
# Run: asyncio.run(load_test(concurrency=10, total_requests=50))
Scaling Strategies
| Strategy |
When |
Implementation |
| Queue-based processing |
> 50 RPM sustained |
Redis/SQS queue + worker pool |
| Model routing |
Mixed workloads |
Haiku for simple, Sonnet for complex |
| Message Batches |
Offline processing |
100K requests, 50% cheaper, no RPM impact |
| Prompt caching |
Repeated system prompts |
90% input token savings |
| Request coalescing |
Duplicate prompts |
Cache identical request hashes |
Horizontal Scaling Pattern
# Multiple application instances sharing the same API key
# Rate limits are per-organization, NOT per-instance
# Use a shared rate limiter (Redis) to coordinate
import redis
r = redis.Redis()
def check_rate_limit(key: str = "claude:rpm", limit: int = 100, window: int = 60) -> bool:
current = r.incr(key)
if current == 1:
r.expire(key, window)
return current <= limit
Error Handling
| Issue |
Cause |
Fix |
| 429 during load test |
Exceeded tier limits |
Reduce concurrency or upgrade tier |
| Increasing latency under load |
Output queue saturation |
Reduce max_tokens |
| Uneven request distribution |
No load balancing |
Use queue for fair distribution |
Resources
Next Steps
For reliability patterns, see anth-reliability-patterns.
1---2name: anth-load-scale3description: |4 Implement load testing, auto-scaling, and capacity planning for Claude API.5 Use when running performance benchmarks, planning for traffic spikes,6 or configuring horizontal scaling for Claude-powered services.7 Trigger with phrases like "anthropic load test", "claude scaling",8 "anthropic capacity planning", "scale claude api".9allowed-tools: Read, Write, Edit, Bash(npm:*), Grep10version: 1.0.011license: MIT12author: Jeremy Longshore <jeremy@intentsolutions.io>13tags: [saas, ai, anthropic]14compatible-with: claude-code15---1617# Anthropic Load & Scale1819## Overview2021Capacity planning and load testing for Claude API integrations. Key constraint: your rate limits (RPM/ITPM/OTPM) are the ceiling, not your infrastructure.2223## Capacity Planning2425```python26# Calculate required tier based on traffic27def plan_capacity(28 requests_per_minute: int,29 avg_input_tokens: int,30 avg_output_tokens: int,31 model: str = "claude-sonnet-4-20250514"32) -> dict:33 itpm = requests_per_minute * avg_input_tokens34 otpm = requests_per_minute * avg_output_tokens3536 # Estimate monthly cost37 pricing = {38 "claude-haiku-4-20250514": (0.80, 4.00),39 "claude-sonnet-4-20250514": (3.00, 15.00),40 "claude-opus-4-20250514": (15.00, 75.00),41 }42 rates = pricing[model]43 cost_per_request = (avg_input_tokens * rates[0] + avg_output_tokens * rates[1]) / 1_000_00044 monthly_cost = cost_per_request * requests_per_minute * 60 * 24 * 304546 return {47 "rpm_needed": requests_per_minute,48 "itpm_needed": itpm,49 "otpm_needed": otpm,50 "cost_per_request": f"${cost_per_request:.4f}",51 "monthly_estimate": f"${monthly_cost:,.0f}",52 "recommendation": "Contact Anthropic sales for Scale tier" if requests_per_minute > 500 else "Self-serve tiers sufficient",53 }5455print(plan_capacity(100, 500, 200))56```5758## Load Testing Script5960```python61import anthropic62import asyncio63import time64from dataclasses import dataclass6566@dataclass67class LoadTestResult:68 total_requests: int = 069 successful: int = 070 failed: int = 071 rate_limited: int = 072 avg_latency_ms: float = 073 p99_latency_ms: float = 074 total_input_tokens: int = 075 total_output_tokens: int = 07677async def load_test(78 concurrency: int = 10,79 total_requests: int = 100,80 model: str = "claude-haiku-4-20250514"81) -> LoadTestResult:82 client = anthropic.Anthropic()83 result = LoadTestResult()84 latencies = []85 semaphore = asyncio.Semaphore(concurrency)8687 async def single_request():88 async with semaphore:89 start = time.monotonic()90 try:91 msg = client.messages.create(92 model=model,93 max_tokens=64,94 messages=[{"role": "user", "content": "Respond with exactly: OK"}]95 )96 duration = (time.monotonic() - start) * 100097 latencies.append(duration)98 result.successful += 199 result.total_input_tokens += msg.usage.input_tokens100 result.total_output_tokens += msg.usage.output_tokens101 except anthropic.RateLimitError:102 result.rate_limited += 1103 except Exception:104 result.failed += 1105 result.total_requests += 1106107 tasks = [single_request() for _ in range(total_requests)]108 await asyncio.gather(*tasks)109110 if latencies:111 latencies.sort()112 result.avg_latency_ms = sum(latencies) / len(latencies)113 result.p99_latency_ms = latencies[int(len(latencies) * 0.99)]114115 return result116117# Run: asyncio.run(load_test(concurrency=10, total_requests=50))118```119120## Scaling Strategies121122| Strategy | When | Implementation |123|----------|------|---------------|124| Queue-based processing | > 50 RPM sustained | Redis/SQS queue + worker pool |125| Model routing | Mixed workloads | Haiku for simple, Sonnet for complex |126| Message Batches | Offline processing | 100K requests, 50% cheaper, no RPM impact |127| Prompt caching | Repeated system prompts | 90% input token savings |128| Request coalescing | Duplicate prompts | Cache identical request hashes |129130## Horizontal Scaling Pattern131132```python133# Multiple application instances sharing the same API key134# Rate limits are per-organization, NOT per-instance135# Use a shared rate limiter (Redis) to coordinate136137import redis138139r = redis.Redis()140141def check_rate_limit(key: str = "claude:rpm", limit: int = 100, window: int = 60) -> bool:142 current = r.incr(key)143 if current == 1:144 r.expire(key, window)145 return current <= limit146```147148## Error Handling149150| Issue | Cause | Fix |151|-------|-------|-----|152| 429 during load test | Exceeded tier limits | Reduce concurrency or upgrade tier |153| Increasing latency under load | Output queue saturation | Reduce max_tokens |154| Uneven request distribution | No load balancing | Use queue for fair distribution |155156## Resources157158- [Rate Limits](https://docs.anthropic.com/en/api/rate-limits)159- [Service Tiers](https://docs.anthropic.com/en/api/service-tiers)160161## Next Steps162163For reliability patterns, see `anth-reliability-patterns`.164