Skip to content

API Error (503 no healthy upstream) · Retrying in 1 seconds… (attempt 1/10)

Nothing about your account is wrong here. This is a routing failure: a proxy in front of the model service looked for a healthy backend to forward your request to and found none. Your key was never checked, your tokens were never counted, and your prompt was never read — which rules out auth errors, quota, context length, and anything you could fix by editing the request.

Data as of 2026-09. Vendor limits and defaults change; check the official docs for current values before acting on any number below.

The tell is the status code that isn’t in the table

Anthropic documents the statuses its API returns: 400, 401, 402, 403, 404, 409, 413, 429, 500, 504 and 529, each mapped to an error.type. 503 is not among them. Neither is the wording — documented errors come back as a JSON envelope with type, message, and a request_id alongside. What you got is a bare phrase in parentheses.

Both facts point the same way: this response was manufactured by something in front of the API, not by the API. “No healthy upstream” is the house style of Envoy-family proxies, emitted when every backend in the pool for that route is marked unhealthy. It is a load balancer reporting on its own view of the world.

The practical consequence is the one worth remembering: there is no request_id, and there never will be for this failure. A request id is minted by the service that processes the request, so its absence is not an oversight in the error message — it is proof of where the request stopped. If you are about to open a support ticket, notice that you have nothing to put in it. That is information, not an inconvenience: it tells you to investigate the path rather than the payload.

Contrast this with a 529 overloaded_error, which is documented and does come from the API. A 529 means the service is up and saturated. A 503 with no healthy upstream means, from the proxy’s perspective, the service is not there at all. Those are different failures with different durations.

The attempt counter is one attempt, repeated

(attempt 1/10) is a retry loop in the tool you are running, not the SDK’s. The official SDKs retry transient failures with exponential backoff twice by default; a counter that climbs toward ten belongs to a layer above that.

Two things follow. First, the banner is not evidence that anything is being diagnosed or corrected — it is the same request being handed to the same resolver, over the same network path, ten times. If the unhealthy pool is constant over those seconds, attempt 10 fails for exactly the reason attempt 1 did. Second, if you have configured your own max_retries, this counter is not it, and changing that setting will not change this banner.

Is retrying useful?

Yes, and it is already happening — the judgement you have to make is when to stop letting it.

An unhealthy upstream pool is usually transient. Backends get marked unhealthy during a deploy, a capacity shift, or a brief network partition, and they get marked healthy again without anyone filing a ticket. Letting the built-in loop run is the correct first response, and it will resolve the majority of these.

The termination condition is the whole point: if the loop exhausts its attempts, the next thing to change is the route, not the number of attempts. Ten failures in a row over several seconds is not bad luck; it means the condition is stable on the timescale of your retries. Running another twenty attempts tests nothing new. Everything below is about changing one variable in the path so that the eleventh attempt is not identical to the first.

Whose proxy is it?

  • Do you point the tool at a custom base URL, a relay, or a third-party gateway? Then the overwhelmingly likely answer is that the unhealthy pool is theirs. Send the same request with the same credentials straight at the official endpoint. If that works, you have found it, and no amount of work on your machine or your account will help.
  • Does the failure come back almost instantly? A proxy that rejects locally answers in milliseconds because it never opened a connection to a backend. A failure that takes noticeably longer implies something was attempted and timed out, which points at the network path instead of at a health-check verdict.
  • Does a trivially small request fail the same way? Send the smallest possible request. If it fails identically, the failure is independent of your payload, and you can stop looking at prompt size, context, and model choice entirely. If small requests succeed while large ones 503, suspect something in the path that buffers whole request bodies.
  • Does it reproduce from a different network? Phone hotspot, a different machine, a cloud shell. Corporate egress proxies, VPN split tunnels and TLS-inspecting middleboxes all sit in exactly the position that emits this message. If the other network is clean, the fault is local to yours.
  • Are other people reporting it at the same moment? Then it is upstream and general, and the answer is to wait. Check the vendor status page before spending an afternoon on your own configuration.

Fix by scenario

  • Custom base URL or gateway — switch to the direct endpoint to confirm, then take it up with whoever runs the gateway. Keep a way to flip between the two; being able to swap the route in one command turns a two-hour investigation into a one-minute one.
  • Local network path — retest from a different network before touching anything. If a VPN or inspection proxy is implicated, exempt the API host, or route around it.
  • Started right after an infrastructure change — new container image, new DNS resolver, new egress rules. Suspect the change, not the vendor. Roll it back far enough to prove which side owns the problem.
  • General and brief — let the loop run, then stop. Long-running work is worth moving off the synchronous path anyway: 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 beyond that.
  • Reporting it — since there is no request id, capture what a proxy failure does leave behind: the exact timestamp with timezone, the base URL actually in effect, the response headers (a proxy usually names itself in them), and whether a plain curl from the same machine reproduces it.

How to confirm it’s fixed

Re-run the request that failed, on the same route, and require it to succeed twice in a row — a single success is indistinguishable from the upstream pool recovering on its own while you were editing config.

If your change was to the route, prove the change took effect rather than assuming it: print the base URL the client is using at runtime, not the value in the file you edited. Environment overrides and cached sessions are the usual reason a route change appears not to work.

If the failure comes back as a 500 with a JSON body and a request_id, your request did reach the service and something inside it broke — that is an internal server error, and the request id in it is worth more than anything on this page. If the message talks about limiting your requests rather than about an upstream, you are being throttled rather than misrouted; see server-side throttling that disclaims your usage limit and, for account-level limits, API Error: Rate limit reached. When you are not sure which of the four you are holding, the AI coding error triage tool sorts them by the signals used above: which status, which envelope, and whether there is a request id at all.