Claude API Timeout Configuration Best Practices
Timeout configuration for the Claude API is less about picking one magic number and more about matching timeout values to the type of request you're making. A short completion with a small max_tokens value behaves very differently from a long tool-use chain or a streamed response — and using the same timeout for both is the most common cause of either premature client-side failures or requests that hang far longer than your infrastructure can tolerate.
The short answer: for non-streaming requests, set your HTTP client timeout to at least 2–3x the expected generation time for your max_tokens limit, with a hard ceiling around 120–180 seconds. For streaming requests, don't apply a single timeout to the whole call — instead set a connection timeout (5–10s) and a separate idle/read timeout between chunks (20–30s), since the total duration of a stream is open-ended by design. The rest of this article explains why, and how to implement it safely.
Why Claude API calls can be slow
Generation time scales with:
- Output length — a 4,000-token response takes meaningfully longer than a 200-token one.
- Model choice — larger models produce higher-quality output but at lower tokens/second than smaller ones.
- Tool use — if the model calls a tool and waits for your function result before continuing, your own tool execution time adds to the total round trip.
- Prompt length — very large context windows add processing time before the first token is produced.
- Load and queuing — occasional backend contention adds latency that's outside your control.
Because of this variability, a fixed timeout tuned for "the median request" will fail intermittently on the requests that matter most — the long ones.
Separate timeouts for connect, first-byte, and total duration
Most timeout bugs come from conflating different phases of the request into one number. Structure your timeout configuration into three layers:
- Connection timeout — how long to wait for the TCP/TLS handshake. This should be short (5–10 seconds). If this fails, it's a network issue, not a model latency issue, and retrying immediately is usually safe.
- Time-to-first-byte (TTFB) / first-token timeout — how long to wait before any data arrives. For non-streaming calls this overlaps with total duration; for streaming calls, measure it separately. 15–30 seconds is reasonable for most prompts.
- Total/idle timeout — for non-streaming responses, this is your overall ceiling (120–180s depending on
max_tokens). For streaming responses, measure idle time between chunks rather than total stream duration, since a long but actively-streaming response is healthy, not stuck.
// Non-streaming request with layered timeouts using fetch + AbortController
const controller = new AbortController();
const totalTimeout = setTimeout(() => controller.abort(), 150_000); // 150s ceiling
try {
const response = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"x-api-key": process.env.ANTHROPIC_API_KEY,
"anthropic-version": "2023-06-01",
"content-type": "application/json",
},
body: JSON.stringify({
model: "claude-sonnet-4-5",
max_tokens: 2048,
messages: [{ role: "user", content: "Summarize this document..." }],
}),
signal: controller.signal,
});
clearTimeout(totalTimeout);
} catch (err) {
if (err.name === "AbortError") {
console.error("Request exceeded total timeout ceiling");
}
throw err;
}
Streaming requires idle-timeout logic, not total-timeout logic
When you stream responses, the connection can legitimately stay open for a long time while tokens trickle in. A naive total timeout will kill valid long-running streams. Instead, track the time since the last received chunk and abort only if that idle gap exceeds a threshold:
async function streamWithIdleTimeout(response, idleMs = 25_000) {
const reader = response.body.getReader();
let lastChunkTime = Date.now();
const idleCheck = setInterval(() => {
if (Date.now() - lastChunkTime > idleMs) {
reader.cancel();
clearInterval(idleCheck);
throw new Error("Stream idle timeout exceeded");
}
}, 2000);
while (true) {
const { done, value } = await reader.read();
if (done) break;
lastChunkTime = Date.now();
// process chunk
}
clearInterval(idleCheck);
}
This pattern — connection timeout, idle timeout, no blanket total timeout — is the right default for any streaming integration, whether you're calling the Claude API directly or through a proxy. If you're using SubToAPI's streaming endpoint (/docs/streaming), the same idle-timeout logic applies on the client side, since the underlying stream behavior is unchanged.
Timeouts and retries need to work together
A timeout without a retry policy just converts transient slowness into a hard failure for your users. But retrying blindly on every timeout can cause duplicate side effects (e.g., double-charged tool actions) and retry storms that make backend load worse.
Best practices:
- Only retry on connection timeouts and clearly transient errors (502/503/529), not on requests that timed out after partial output was already streamed.
- Use exponential backoff with jitter (e.g., 1s, 2s, 4s + random offset), capped at 2–3 attempts.
- Make tool-use side effects idempotent where possible, so a retried request doesn't double-execute an action.
- Log timeout events separately from error events — a spike in timeouts without a spike in 4xx/5xx responses usually points to
max_tokensor prompt size growth, not an API outage.
Tuning timeouts to max_tokens
A practical rule of thumb: budget roughly 15–40ms per output token depending on model, and add a 20-second buffer for network and queuing variance. If you allow max_tokens: 4096, a hard timeout below 60 seconds will produce false failures on legitimate long responses. If your use case genuinely needs short, bounded latency (e.g., a chat UI), lower max_tokens itself rather than fighting it with an aggressive timeout — that's a cleaner and more predictable fix.
If you're building this across multiple services or team members and want timeout behavior, retries, and streaming handled consistently without re-implementing this logic everywhere, routing requests through SubToAPI (/docs) gives you a single HTTPS endpoint with consistent streaming semantics and usage metadata, so each service doesn't need its own bespoke timeout logic.
Summary checklist
- Set connection timeouts short (5–10s), total/idle timeouts generous and tied to
max_tokens. - Never apply a single total timeout to a streaming response — use idle-between-chunks logic instead.
- Retry only on connection-level failures, with backoff and a low attempt cap.
- Lower
max_tokensinstead of tightening timeouts if you need predictable low latency. - Monitor timeout rates separately from error rates to catch configuration drift early.
FAQ
What's a safe default timeout for the Claude API? For non-streaming requests, 60–120 seconds covers most use cases with max_tokens under 4,000. Scale it up if you allow longer outputs, and always separate connection timeout from total timeout.
Should I use the same timeout for streaming and non-streaming requests? No. Streaming responses should use an idle timeout (time since the last chunk, typically 20–30s) rather than a fixed total duration, since legitimate streams can run much longer than any single-request timeout.
Should I automatically retry on every timeout? Only retry on connection-level or clearly transient failures. If a request already received partial streamed output before timing out, retrying it risks duplicate side effects — handle those cases with idempotent tool logic instead.