Claude API Throughput Optimization Tips
Throughput on the Claude API is bound by three things: how many requests you can run concurrently, how much of each request is wasted on redundant tokens or idle waiting, and how gracefully your client handles rate limits and retries. Optimizing for throughput means attacking all three at once — not just adding more parallel requests, since that alone usually trips rate limits or burns budget without improving real completion time.
If you're here because your app feels slow under load or you're hitting 429s during traffic spikes, the fixes below are ordered by impact: the first few give the biggest wins with the least code change.
Stream Responses Instead of Waiting for Full Completions
The single biggest perceived-throughput win is streaming. Without streaming, your app waits for the entire response to generate before it can do anything — for long outputs that's several seconds of dead time per request. With streaming, you start processing tokens the moment they arrive.
curl -N https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-sonnet-4-5",
"max_tokens": 1024,
"stream": true,
"messages": [{"role": "user", "content": "Summarize this document"}]
}'
Streaming doesn't reduce total tokens generated, but it reduces time-to-first-byte dramatically, which is what users and downstream systems actually perceive as speed. If you're building on top of SubToAPI, the same pattern applies through /v1/messages with stream: true — see /docs/streaming for the event format.
Cut Input Tokens Before You Cut Anything Else
Throughput is often a token-count problem disguised as an infrastructure problem. Every token you send as context has to be processed before generation starts. Common sources of bloat:
- Repeating the full system prompt on every call when it never changes
- Sending entire conversation history instead of a summarized rolling window
- Including tool schemas the model doesn't need for that specific turn
- Pasting raw JSON/XML when a trimmed or flattened version would do
Trimming 30% of your input tokens doesn't just save money — it directly reduces processing time per request, which increases how many requests you can push through in a fixed time window.
Use Prompt Caching for Repeated Context
If your requests share a large, stable prefix — a long system prompt, a document, a tool definition block — prompt caching lets the model skip reprocessing that prefix on every call. This is one of the most underused throughput levers because it's easy to bolt on without restructuring your app logic. The rule of thumb: anything that's identical across requests belongs at the front of the prompt, and anything unique to the request goes after it.
Parallelize Within Your Rate Limit, Not Around It
Concurrency helps throughput only up to your account's rate limit. Beyond that, extra parallel requests just queue up and return 429s, which then need retries — net negative. Instead of guessing, track your retry-after headers and dynamically size your concurrency pool.
const queue = [];
let active = 0;
const MAX_CONCURRENT = 8;
async function runTask(task) {
while (active >= MAX_CONCURRENT) {
await new Promise(r => setTimeout(r, 50));
}
active++;
try {
return await task();
} finally {
active--;
}
}
Start with a conservative concurrency ceiling, monitor 429 rates, and increase gradually. A sawtooth pattern (ramp up, back off on errors, ramp up again) will outperform a fixed high number that constantly gets throttled.
Batch Independent Requests Instead of Chaining Them
If you're processing a list of independent items — classifying 500 support tickets, summarizing 200 documents — don't process them sequentially in a single loop that awaits each call before starting the next. Fire them concurrently (bounded by the pool above) so the latency of one request overlaps with the processing time of others. Sequential loops turn N requests into N times the latency; bounded concurrency turns it into roughly (N / concurrency) times the latency.
Right-Size the Model for the Task
Throughput isn't only about how fast a single call returns — it's about total work done per unit time across your whole system. A smaller, faster model handling routine classification or extraction frees up your rate limit and reduces average latency, leaving your larger model's capacity for tasks that actually need deeper reasoning. Mixing models by task is often a bigger throughput win than any single optimization on a single model call.
Handle Retries with Backoff, Not Brute Force
Naive retry logic (retry immediately, retry on a fixed 1-second delay) actively hurts throughput under load because it adds more requests right when the API is already rate-limiting you. Exponential backoff with jitter spreads retries out so they don't stack on top of each other:
async function withBackoff(fn, maxAttempts = 5) {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
return await fn();
} catch (err) {
if (attempt === maxAttempts - 1) throw err;
const delay = Math.min(1000 * 2 ** attempt, 15000) + Math.random() * 300;
await new Promise(r => setTimeout(r, delay));
}
}
}
Where SubToAPI Fits
If you're already routing traffic through SubToAPI, the same principles apply directly — issue separate sub_live_... keys per service so each one gets its own usage tracking, watch per-key metadata in the dashboard to spot which endpoints are token-heavy, and use streaming via /docs/streaming for latency-sensitive paths. Team and Scale plans (see /pricing) also make it easier to split concurrency across multiple keys instead of funneling every service through a single bottleneck key. Setup takes a few minutes — see /docs/quickstart if you're starting fresh.
questions
Does increasing concurrency always increase throughput? No. Once you're at your rate limit, extra concurrent requests just queue and eventually 429, adding retry overhead instead of completing more work. Size concurrency to your actual limit and monitor error rates as you scale it up.
Is streaming actually faster, or just perceived as faster? Total generation time is roughly the same, but time-to-first-token drops sharply, and you can start downstream processing before the full response lands — which is a real throughput gain in pipelines, not just a UX trick.
What's the fastest way to reduce latency per request? Cut input tokens first (trim history, drop unused schema, cache stable prefixes), then pick the smallest model that reliably handles the task — both reduce processing time per call more than infrastructure changes do.