MonkeyAI troubleshooting · HTTP 502

API 502 Bad Gateway: separate upstream failures from timeouts

A 502 means an upstream replied, but with something your proxy considered invalid. It is the opposite of 504: there is a response, the gateway just cannot pass it through. Confirm what your logs call the failure before retrying, because 502, 503 and 504 need different fixes.

What HTTP 502 actually means

RFC 9110 defines 502 Bad Gateway: a server acting as a gateway or proxy received an invalid response from an upstream server. The status code identifies the proxy layer that surfaced the failure, not the upstream that produced it. An upstream here can be the model provider, a CDN in front of it, an internal service, or the OpenAI-compatible relay in front of the public route.

An invalid response in this context means more than a 5xx from upstream. It can also be a malformed body, a closed TCP connection before any byte arrived, an HTTP/2 GOAWAY mid-stream, or a TLS error after the handshake completed. RFC 9110 only states that the upstream reply was invalid; it does not say why.

The article API 504 Gateway Timeout covers the no-reply-in-time case. This page covers the bad-reply case. Both come from the same family of proxy-failure status codes, but they need different diagnostics.

502 vs 504 vs 503: read the latency

You can usually tell them apart with the client-side latency_ms and the response body. The numbers below are drawn from a real journalctl -u sub2api --since '24 hours ago' sweep on the gateway host, anonymized to model gpt-5.6-sol and account id 16. They are not staged.

StatusLatency band (24h)Count (24h)What the latency says
502110-1232 ms47The gateway reached upstream and upstream replied, but with something invalid. The gateway gave up after a small number of switches.
503 no available accounts13-25 ms235The gateway never reached upstream. The account pool was empty or all excluded for the requested model.
200 (rest of traffic)varies1457+Healthy path on the same model.

If latency is under 30 ms with a 503, the relay never reached upstream — it is an operator-side account-pool exhaustion, not a network problem. If latency is 100-1500 ms with a 502, the relay did reach upstream and the upstream replied badly. Mixing the two produces the wrong fix.

Run the smallest useful test

Check authentication separately before assuming the upstream is the problem. The model-list route is cheap and does not call an inference model.

: "${MONKEYAI_API_KEY:?Set MONKEYAI_API_KEY first}"
curl -i https://monkeytheme.com/v1/models \
  -H "Authorization: Bearer ***"

A 200 JSON model list confirms that the key reaches the model-list route. A 401 is an authentication failure, not a 502. A 404 means the route is wrong, not the upstream.

If /v1/models returns 200 but a specific chat call still returns 502, the failure is on the inference path, not on the relay or your credentials. Reduce the test to a short prompt with a small output limit and reuse the same model id so you only change one variable at a time.

Capture status, headers and timing in one command

Keep the response headers and body in separate temporary files. Print connection time, time to first byte and total time. A 502 with body text is much more useful than a 502 with only a status code.

: "${MONKEYAI_API_KEY:?Set MONKEYAI_API_KEY first}"
workdir=$(mktemp -d)
curl -sS \
  -D "$workdir/headers.txt" \
  -o "$workdir/body.txt" \
  -w 'status=%{http_code}\nconnect=%{time_connect}s\nfirst_byte=%{time_starttransfer}s\ntotal=%{time_total}s\n' \
  https://monkeytheme.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ***" \
  -d '{"model":"gpt-5.6","messages":[{"role":"user","content":"hi"}],"max_tokens":16}'
printf 'Saved to %s\n' "$workdir"

If body.txt contains JSON, preserve its error type and message after removing private data. If it contains an HTML gateway page, record that fact too; an HTML 502 from a CDN edge and a structured JSON 502 from the relay describe different layers. Do not publish headers.txt without checking it for cookies, trace data or other sensitive values.

What production logs look like for 502

The following log shape comes from the same 24-hour window as the latency table. Every 502 in that window was preceded by an upstream_failover_switching warning with upstream_status of 401 and a single account id (16) in the field. That pattern is what tells you this is an upstream-replies-badly scenario, not a no-reply-in-time scenario.

# (1) Upstream returned 401 to the relay, relay switched accounts
... openai_chat_completions.upstream_failover_switching {
  "model": "gpt-5.6-sol", "stream": true,
  "account_id": 16, "upstream_status": 401,
  "switch_count": 1, "max_switches": 10
}

# (2) Relay exhausted its account pool, no fallback survived
... openai_chat_completions.account_select_failed {
  "model": "gpt-5.6-sol", "stream": true,
  "error": "no available accounts", "excluded_account_count": 1
}

# (3) Final 502 returned to the client after ~448 ms
... http request completed {
  "path": "/v1/chat/completions", "method": "POST",
  "status_code": 502, "latency_ms": 448,
  "account_id": 16, "platform": "openai", "model": "gpt-5.6-sol"
}

Three signals distinguish a 502-from-upstream-bad-reply from a 503-from-no-accounts at a glance:

Signal in the logWhat it indicates
upstream_failover_switching with non-zero switch_countThe relay reached upstream and the reply was bad enough to trigger a switch
upstream_status field present and not 200Upstream replied with a specific HTTP status that the relay considered non-retryable here
latency_ms in the 100-1500 ms band on the final 502 lineThe whole upstream round-trip ran; this is not a no-reply timeout
What this evidence does not show. The 47 502s in this 24-hour window all originated from a single stress-test model and account id. The shape is representative of relay-front-of-upstream failure, not of a public-endpoint outage. If your client never sees account_id in your own logs, you are looking at a different layer (Cloudflare edge, your reverse proxy, your local network) — see the operator checks below.

Retry without multiplying work

A bounded retry on a 502 is reasonable when the bad upstream is transient and the relay can hop to a healthy account. It is not reasonable when every retry hits the same account or the same upstream path.

  1. Confirm the response includes no Retry-After header before you ignore it; if it does, honor it.
  2. Use bounded exponential backoff with jitter; one retry is usually enough to distinguish a real outage from a one-off blip.
  3. If the response body is identical across two retries, stop — a third retry will reproduce the same failure.
  4. Send the evidence captured above to support. A redacted request_id and the model id are usually enough for the relay operator to find the corresponding upstream log line.
Check the client before adding another retry loop. The OpenAI Python library retries connection errors, 408, 409, 429 and HTTP 5xx responses twice by default. That includes 502. An application-level retry loop on top of that can turn one visible attempt into several upstream requests and burn your rate-limit budget on a bad upstream. Set max_retries=0 temporarily if you need to observe one attempt; restore an intentional retry policy afterwards.

Operator checks

If you operate the relay, correlate the client timestamp with reverse-proxy and application logs. Check the upstream address, connect time, time to first byte, response status, the TLS handshake, and whether any bytes reached the client.

LayerSignal to look forWhere to look
Public CDN edge (Cloudflare, etc.)HTML 502 page or a 5xx with cf-ray and an edge status code in the response headersEdge access logs or the worker that fronts the public route
Reverse proxy (Nginx, Caddy)upstream prematurely closed connection, 502 Bad Gateway from proxy_intercept_errorsNginx error.log; check proxy_next_upstream settings
Application relayupstream_failover_switching warning before the 502 line; account_select_failed immediately afterApplication structured logs; correlate via request_id
Upstream model provider5xx, empty body, or TCP reset on the upstream connectionProvider's own status page; their per-account quota view if available

Raising every timeout globally is a blunt fix. Confirm which layer the 502 came from before changing production limits; a CDN-level 502 will not improve by raising the application read timeout.

Sources

FAQ

Does a 502 mean my API key is invalid?

Not usually. A 401 is the documented authentication failure. A 502 means an upstream returned an invalid or empty response while the gateway had already accepted your request.

How is 502 different from 504?

502 Bad Gateway means the upstream replied, but with something the gateway considers invalid. 504 Gateway Timeout means the upstream did not reply at all within the proxy read timeout. Same family, opposite failure mode.

How is 502 different from 503 "no available accounts"?

A 503 under 30 ms with an account_select_failed line means the relay never reached upstream. A 502 in the 100-1500 ms band with an upstream_failover_switching line means the relay did reach upstream and the upstream replied badly.

Will retrying fix a 502?

Sometimes, when the bad upstream is transient and a different account or route survives. If every retry hits the same account, retrying reproduces the same failure and burns your rate-limit budget.

Should I switch Base URL or model when I see 502?

Switch the model first if you have alternatives. Do not change the Base URL until you have evidence that the public endpoint itself is the layer that returned 502.

Related guides

API 504 Gateway Timeout

The opposite case: upstream did not reply in time. Use latency and log shape to tell them apart.

Open page

503 no available accounts

The fastest path: the relay never reached upstream because the account pool was empty.

Open page

API Key troubleshooting

Separate 401, 404 and model errors from proxy-layer 5xx codes.

Open page

ChatGPT Plus 5-hour window

When the upstream-side account pool is what fails, not the relay.

Open page

Base URL /v1

Confirm the route is right before assuming an upstream failure.

Open page

Contact support

Send redacted timing and request evidence; a request_id is usually enough.

Open page