There are usually three timeouts between you and the model — your SDK's, an intermediary's, and the provider's own rule about long non-streaming calls — and the shortest one wins. Raising only the SDK's is why this keeps happening after you thought you fixed it.
The error
# Rejected before generating, for a long non-streaming request:
{"type":"error","error":{"type":"invalid_request_error",
"message":"Streaming is strongly recommended for operations that may take
longer than 10 minutes."}}
# Or the client-side companion, with no response at all:
APITimeoutError: Request timed out.Causes and fixes at a glance
| Cause | Fix |
|---|---|
| A long non-streaming generation | Stream it. Long completions are expected to stream, not to be waited on. |
| SDK client timeout shorter than the generation | Raise it — but raise the intermediary's limit too, or nothing changes. |
| A proxy, load balancer or serverless function capping the request | Find the shortest limit in the chain; that is the one you are hitting. |
| Connection accepted, then never answered | A hang, not a slow answer. Bound time-to-first-byte separately. |
Stream anything that could run past a couple of minutes
Beyond avoiding the limit, streaming gives you a liveness signal: tokens arriving means the model is working, so a hang becomes distinguishable from slow progress. With a single blocking call those two look identical until the timeout fires.
Raise every timeout in the chain, not just the SDK's
People almost always change the client and stop there. If a reverse proxy or a serverless platform caps the request below your new client timeout, the cap still wins and the symptom does not change.
from anthropic import Anthropic
# Client timeout is only one of the limits in play.
client = Anthropic(api_key=KEY, timeout=600.0)
with client.messages.stream(
model="claude-sonnet-5",
max_tokens=8192,
messages=[{"role": "user", "content": prompt}],
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)Bound time-to-first-byte separately from total duration
These are two different failures needing two different remedies. A short first-byte deadline catches a dead connection quickly; a generous total deadline lets a genuinely long generation finish. One combined timeout cannot do both.
Make retries safe before you add them
A request that timed out on your side may have completed upstream. If the work has side effects, or you are charged per call, add idempotency at your layer before adding a retry loop.
If you’re calling through Kunavo
Kunavo publishes its own numbers rather than leaving you to discover them: it waits at most 240 seconds for upstream response headers, and the /v1/messages, /v1/chat/completions and /v1/responses routes are capped at 300 seconds. So a non-streaming call that would run longer will not complete through the gateway — stream it. The 240-second bound applies to time-to-headers only and is released the moment headers arrive, so it never truncates a long stream. The synchronous media routes are the deliberate exception at 600 seconds, because image, video and music renders legitimately take minutes. A request that times out before headers counts as a channel failure, is retried once on the model's other channel where one is configured, and is recorded at zero cost.
FAQ
Does a timed-out request get billed?
On Kunavo, no — failed requests are recorded at zero cost. Billing direct with a provider depends on whether generation actually happened.
Why does streaming avoid the limit?
The response starts within seconds and the connection stays busy, so no single stretch of silence is long enough to trip a timeout.
What should my client timeout be?
Longer than your worst realistic generation, paired with a short separate first-byte deadline. One long combined timeout turns every hang into a multi-minute stall.
Related guides
- LLM streaming errors — SSE cutoffs, hanging streams and missing usage
- Claude API 529 overloaded_error — what it is and how to ride it out
More error semantics live in the error reference; getting a key takes a minute via sign up and the authentication docs.