anthropic.APIConnectionError: Connection error
APIConnectionError is the one class in the SDK’s exception hierarchy that does
not carry an HTTP response. Every other failure you can hit — 401
authentication_error, 402 billing_error, 429 rate_limit_error, 529
overloaded_error — arrives as a JSON envelope with a type, a message and a
request_id alongside. This one has none of those, which rules out your key,
your billing, your quota and platform overload in a single stroke: the server
never got far enough to have an opinion about any of them.
Data as of 2026-09. Vendor limits and defaults change; check the official docs for current values before acting on any number below.
“Connection error” is a constant, not a report
The text after the colon is a fixed string the client raises. It is not something Anthropic sent, it does not vary with the cause, and searching it against the vendor’s error tables will never match — because the vendor’s error tables only describe responses, and no response exists here.
The actual failure is attached to the exception as its underlying cause. In
Python that is __cause__; before you form any theory, print it:
except anthropic.APIConnectionError as e:
print(type(e.__cause__).__name__, repr(e.__cause__))
A DNS lookup that resolved nothing, a refused connection, a certificate that would not validate, a socket reset by a middlebox, and a timeout waiting for the first byte all surface as this same sentence and have completely different fixes. Guessing between five causes with identical top-level text is a coin flip you can simply decline to flip.
Where it was raised decides how much has already been tried
This is the part that changes what you should do, and the exception itself gives no hint of it. Look at the traceback frame, not the message.
Raised from the request call, before any content. The official SDKs retry
transient failures — connection errors, rate limits, 5xx — with exponential
backoff, twice by default, honoring retry-after when present. So the
exception reaching your handler is not the first failure. It is the third. The
condition survived two backoff intervals, which makes it far closer to
deterministic than a single network blip, and it is the strongest argument
against your first instinct of running it again.
Raised while iterating a stream. Mid-stream failures arrive after the response returned 200, and they do not go through that retry mechanism at all. The exception is the first and only failure. Nothing has been attempted twice, and a genuinely unlucky one-off looks exactly like a systematically broken path.
The same exception therefore means “three attempts failed” in one place and
“one attempt failed” in the other. That is the single most useful fact on this
page, and reading one line of the traceback is how you get it. It also explains
a result that otherwise looks absurd: raising max_retries visibly helps some
teams and does nothing at all for others, on what appears to be the same error.
Is retrying useful?
Yes once — but check the traceback first, because you may have already retried three times without knowing.
If the exception came from the streaming loop, retry once; that half has had no automatic attempts and transient blips are common. If it came from the request call, a retry is your fourth attempt, and you should treat a second failure as proof of a stable condition rather than as bad luck.
Either way the termination rule is the same: two identical failures means stop
and diagnose. There is no retry-after to honor — that header only exists on
responses, and you did not get one — and Anthropic publishes no base delay or
cap in seconds to copy, only the instruction to use exponential backoff. Any
specific wait you insert is a number you invented.
The cheap wrong fix here is raising max_retries and moving on. On the
non-streaming path it multiplies the time you spend failing; on the streaming
path it does nothing whatsoever, because that setting is not consulted once
bytes are flowing.
Telling the causes apart
Each of these has an observation that confirms or rules it out.
It fails instantly, with no visible wait. Nothing was attempted over the network for long. Certificate validation and DNS failures are decided locally and return immediately; a timeout or a reset takes time to happen. Time the failure — an error that lands in well under a second is not a slow network.
The very first request of a fresh process fails. Configuration, not connectivity. Proxy settings, a certificate bundle and a resolver are evaluated the same way on every request, so a path that never works once is a path that was never set up. This is also the case where the browser opening the same page proves nothing — a browser uses the OS proxy settings and the OS certificate store, and a language runtime generally uses neither.
Requests succeed for a while and then this starts. The inverse of the above, and it exonerates configuration entirely: nothing in the configuration changed between the request that worked and the request that failed. Look at connection reuse, a network that changed underneath you, or an intermediary that reaped an idle socket.
A proxy is set in your shell but the client is constructed by hand. An HTTP client only uses a proxy it was told about. If you pass your own transport or client object, environment-based proxy discovery can be bypassed silently, and you end up with a proxy configured everywhere except in the code path that makes the request. Print the client’s effective proxy at runtime rather than trusting the variable you exported.
Only one network is affected. Reproduce the same call from a phone hotspot or a different machine. This is the cheapest test on the page and it is the one people skip, usually because they are already sure it is the vendor.
Fix by scenario
- Certificate validation in the cause — make the runtime trust your organization’s certificate authority. Do not disable verification; that converts a visible failure into a silent exposure of every credential the process handles, on every network it ever joins.
- DNS or connection refused, first request onward — fix the resolver or the egress rule, then re-test from a fresh process so you are not reading a cached success.
- Proxy configured but not used by the client — set it on the client explicitly rather than relying on discovery, and assert the effective value in your logs at startup.
- Raised from the streaming loop, failure point wanders — write your own retry around the whole streaming call, because nothing below you will do it. Keep it deliberate: a replay regenerates from the beginning, so any side effect your code already performed on partial output happens twice.
- Raised from the streaming loop, failure point is consistent — you are most likely looking at a timeout on the path rather than a transport fault. Connection closed mid-response covers that diagnosis in full, including why a 200 arriving before the first token puts the whole failure outside the SDK’s retry machinery.
- Long work, non-streaming — the SDKs validate that non-streaming Messages API requests are not expected to exceed a 10-minute timeout, and the docs recommend streaming or the Message Batches API past that. If your request is genuinely in that territory, the fix is architectural.
How to confirm it’s fixed
Run the failing call from a fresh process, on the network that was failing, and require it to succeed twice in a row. The fresh process matters more here than usual: environment variables exported by hand in one shell and connection state cached in a long-running one are the two standard ways a fix appears to work and then evaporates on the next deploy.
Make it falsifiable by asserting on the cause rather than on the outcome. Keep
the handler that prints __cause__ in place, and consider the problem closed
when a full working session produces no occurrence of it — not when one request
happens to go through.
Related errors
Neighbouring failures in this cluster look alike in a terminal and are not the same thing. If the CLI is printing a retry banner with an attempt counter rather than raising an exception into your code, read the connection error retry loop — the counter there is the tool’s own, not the SDK’s, and its presence carries information. If your stream is being cut after a measurable quiet interval that you can move by editing a number, you have a stream idle timeout instead, which is a deliberate give-up rather than a transport fault. And if the client is still waiting and announcing retries rather than failing, see No response from API, retrying in 2m 25s. When you cannot tell which one you are holding, the AI coding error triage tool sorts them by the signals used above: whether a response ever arrived, how quickly the failure landed, and whether it moves between attempts.