← Back to Knowledge Base

Advanced Claude API Optimization & Token Management

1. Prompt Cache Principles & Maximization

Anthropic's Prompt Caching feature can reduce costs by 90% and latency by 85% for repeated context. Understanding cache behavior is critical for API optimization:

Cache Behavior Rules

  • Minimum Cache Size: Cached blocks must be ≥ 1024 tokens. Smaller blocks are not cached.
  • Cache TTL: Cached content expires after 5 minutes of inactivity. Re-using the same cache within 5 minutes extends the TTL.
  • Cache Key: Cache is keyed by exact content match. Changing a single character invalidates the cache.
  • Cache Placement: Only system role messages and the final user message support caching. Intermediate messages cannot be cached.

Optimal Cache Strategy

const systemPrompt = `You are an expert software architect...
[Large 5000-token context that rarely changes]`;

// Mark system prompt for caching
const response = await anthropic.messages.create({
  model: "claude-sonnet-4.5-high",
  max_tokens: 2048,
  system: [
    {
      type: "text",
      text: systemPrompt,
      cache_control: { type: "ephemeral" }, // Cache this block
    },
  ],
  messages: [
    { role: "user", content: "Refactor this function..." },
  ],
});

2. Token Billing Optimization & Context Management

Claude API pricing is asymmetric: input tokens cost less than output tokens, and cached tokens cost 90% less than input tokens. Structure conversations to maximize caching:

Cost Comparison (Claude Sonnet 4.5 High)

Token Type Cost per 1M Tokens Relative Cost
Output Tokens $15.00 100x
Input Tokens $3.00 20x
Cached Input Tokens $0.30 2x
Cache Write Tokens $3.75 25x

Long Context Window Management

// Sliding window approach for multi-turn conversations
function maintainContextWindow(history: Message[], maxTokens: number = 180000) {
  let totalTokens = estimateTokenCount(history);
  
  while (totalTokens > maxTokens && history.length > 2) {
    // Remove oldest non-system messages first
    history.splice(1, 2); // Remove one user-assistant pair
    totalTokens = estimateTokenCount(history);
  }
  
  return history;
}

3. Concurrency Control & Rate Limit Handling

Anthropic enforces organization-level rate limits. Implement client-side concurrency control to avoid 429 errors:

Token Bucket Rate Limiter

class RateLimiter {
  private tokens: number;
  private lastRefill: number;
  private refillRate: number; // tokens per second
  private capacity: number;

  constructor(requestsPerMinute: number) {
    this.capacity = requestsPerMinute;
    this.tokens = requestsPerMinute;
    this.lastRefill = Date.now();
    this.refillRate = requestsPerMinute / 60;
  }

  async acquire(): Promise {
    this.refill();
    
    while (this.tokens < 1) {
      const waitTime = (1 - this.tokens) / this.refillRate * 1000;
      await new Promise(resolve => setTimeout(resolve, waitTime));
      this.refill();
    }
    
    this.tokens -= 1;
  }

  private refill(): void {
    const now = Date.now();
    const elapsed = (now - this.lastRefill) / 1000;
    this.tokens = Math.min(this.capacity, this.tokens + elapsed * this.refillRate);
    this.lastRefill = now;
  }
}

const limiter = new RateLimiter(50); // 50 requests per minute

async function callClaude(prompt: string) {
  await limiter.acquire();
  return await anthropic.messages.create({...});
}

4. Streaming Output & Retry Best Practices

Streaming responses reduce time-to-first-token and enable progressive rendering. Combine with exponential backoff for robust error handling:

Streaming with Retry Logic

async function* streamWithRetry(prompt: string, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const stream = await anthropic.messages.stream({
        model: "claude-sonnet-4.5-high",
        max_tokens: 4096,
        messages: [{ role: "user", content: prompt }],
      });

      for await (const chunk of stream) {
        if (chunk.type === "content_block_delta") {
          yield chunk.delta.text;
        }
      }
      
      return; // Success, exit retry loop
      
    } catch (error: any) {
      if (error.status === 529 && attempt < maxRetries - 1) {
        // Overloaded error, retry with backoff
        const delay = Math.min(1000 * Math.pow(2, attempt), 30000);
        await new Promise(resolve => setTimeout(resolve, delay));
      } else {
        throw error;
      }
    }
  }
}