ChatGPT Plus 5-hour window: why the OAuth pool returns 503
If your OpenAI-compatible relay returns HTTP 503 with {"error":{"message":"Service temporarily unavailable","type":"api_error"}} and the relay logs say openai_chat_completions.account_select_failed error="no available accounts", the failure usually is not your key. It is the ChatGPT Plus OAuth pool rolling 5-hour window hitting zero on a single account while failover runs out of alternatives.
What the rolling 5-hour window actually is
ChatGPT Plus accounts are subject to a rolling usage window that OpenAI resets on roughly a 5-hour cadence. The exact published budget is not part of OpenAI's documented error surface for API clients — there is no row for 504 or for "rolling window exhausted" on the OpenAI platform's Error codes guide. What you observe from the relay side is a moving quota that drops to zero on a single Plus account once sustained traffic passes through it.
The window is per-account, not per-model. Switching from one model to another on the same Plus OAuth login does not reset the timer, and it does not give you a fresh budget.
Why your client gets 503, not 429
The relay does not surface OpenAI's internal quota signal as a 429 to your client. The chain we observed in production on Aug 7, 2026 looked like this:
# abbreviated relay log (actual lines from journalctl -u sub2api)
WARN openai_chat_completions.upstream_failover_switching
upstream_status=401 account_id=16 switch_count=1 max_switches=10
model=gpt-5.6-sol stream=true
WARN openai_chat_completions.account_select_failed
error="no available accounts" excluded_account_count=0
INFO http request completed status_code=503 latency_ms=16 model=gpt-5.6-sol
Step by step:
- The relay picks a ChatGPT Plus account (account_id 16 in our logs) and forwards the request.
- OpenAI Plus returns 401 to the relay, meaning the rolling window is exhausted and the account's token is effectively revoked for inference traffic.
- The relay marks the account exhausted and tries failover — up to 10 switches in our observed config.
- Every other Plus account in the pool is in the same shape, so each switch returns 401 too.
- After the switch budget runs out, the relay returns 503 with a JSON body that mentions "no available accounts".
The relay absorbs the OpenAI Plus quota signal and translates it into its own 503. Your client never sees the upstream 401 directly.
Why this is different from a 401 to your client
| What you see | Where the failure lives | Where to look |
|---|---|---|
| 401 JSON from the relay, body mentions invalid token | Your key or header | Authorization header, key prefix, account on the relay |
| 404 from the relay | Route mismatch | Base URL, endpoint path, model id |
| 429 with structured body and Retry-After | Single-account quota on a healthy upstream | Drop concurrency; honor Retry-After |
| 503 with "no available accounts" after upstream 401s | Relay's Plus OAuth pool — rolling 5-hour window | Server log: upstream_failover_switching then account_select_failed |
| 504 from the relay | Reverse-proxy timer | proxy_read_timeout, route, upstream latency |
A simple way to confirm before debugging your own config: /v1/models with the same key returns 200 with a JSON model list. If that route passes and /v1/chat/completions returns 503, the failure is in the inference account pool, not in your request shape.
Read the relay log the right way
Three log lines together pinpoint the ChatGPT Plus 5-hour window failure. Look for them in order:
| Log line | What it tells you |
|---|---|
openai_token_cache_get_failed account_id=16 error="redis: nil" | The relay failed to read a cached token for that account. Often the first sign that an account is being recycled. |
openai_chat_completions.upstream_failover_switching upstream_status=401 account_id=16 | The relay tried that account, got a 401 from OpenAI, and is moving to the next account in the pool. |
openai_chat_completions.account_select_failed error="no available accounts" excluded_account_count=0 | The pool is empty. Zero means no account was even attempted at the final selection step — the relay gave up before re-trying. |
If excluded_account_count is greater than zero, failover actually tried every account and each was excluded. If it is zero, the relay never even started a second selection cycle. Both numbers map to the same root cause: the ChatGPT Plus pool cannot serve the request right now.
What to do as a client
- Stop the tight retry loop.
max_retries=0for the duration of the diagnostic. - Drop concurrency by at least half. The window is rate-based, not request-count-based.
- Wait for the rolling window. Recovery is gradual — a 09:00 burn gives partial recovery at 14:00, not full recovery.
- If your workload is steady and you cannot tolerate the cycle, switch to a different tier: an API key from a paid OpenAI API plan does not share the Plus 5-hour window.
- Capture request_id, UTC time, model, and the relay's "no available accounts" body before contacting support. Redact the API key.
***. Capture the request_id and the relay's account_select_failed log line — those are the strongest lookup keys.What an operator should check
If you operate the relay, the same production pattern gives you a smaller set of real actions:
- Confirm the failing requests all share
account_idon the Plus OAuth pool, not the API-key pool. - Check whether the Antigravity token refresher is logging
Account N needs refreshon schedule. If refreshes are healthy and upstream still returns 401, the issue is upstream quota, not token caching. - Track
upstream_failover_switchingcount per day. A sustained count (we saw 241 in 7 days, roughly stable per day) is the early-warning signal that the Plus pool is at its 5-hour ceiling. - Tell the user to reduce concurrency; do not raise the relay's
proxy_read_timeoutin response to this failure. The timer is irrelevant when the upstream already returned 401.
FAQ
What is the ChatGPT Plus 5-hour window?
It is the rolling quota that OpenAI applies to a Plus account. The published budget is not part of OpenAI's documented error surface. Observable behavior: a moving window that drops to zero when traffic through a single Plus account is sustained.
Why does my client see 503 instead of 429?
The relay absorbs the OpenAI Plus signal. After upstream 401s and failover exhaustion, the relay answers the client with 503 "no available accounts".
Does rotating to a different model fix it?
No. The window is per-account, not per-model. Same Plus account, same window.
Will waiting 5 hours unblock it?
The window is rolling. A user who burned budget at 09:00 sees partial recovery at 14:00, not full recovery. Recovery is gradual.
Should I retry harder when I see this 503?
No. Drop concurrency, wait for the rolling window, or move to a different account tier.
How do I confirm it on the server side?
Look for upstream_failover_switching with upstream_status=401, followed by account_select_failed with error="no available accounts".
Is this the same as a 401 to my client?
No. A 401 to your client means your key is bad. A 503 "no available accounts" after upstream 401s means the gateway's Plus OAuth pool is the exhausted layer.
Related guides
Fix 503 "no available accounts"
General pool-exhaustion recovery checklist on an OpenAI-compatible API.
Open page