BerriAI/litellm · error · GetAccessTokenError
Token exchange failed: {exc}
Error message
Token exchange failed: {exc} What it means
Raised as `GetAccessTokenError` when the final OAuth token-exchange request (`POST` to `CHATGPT_OAUTH_TOKEN_URL` with the authorization code) returns an HTTP error status. This happens after successful device approval, while converting the authorization code into access/refresh/id tokens.
Source
Thrown at litellm/llms/chatgpt/authenticator.py:267
try:
client: Final = _get_httpx_client()
redirect_uri: Final = f"{CHATGPT_AUTH_BASE}/deviceauth/callback"
body: Final = (
"grant_type=authorization_code"
f"&code={code_data['authorization_code']}"
f"&redirect_uri={redirect_uri}"
f"&client_id={CHATGPT_CLIENT_ID}"
f"&code_verifier={code_data['code_verifier']}"
)
resp: Final = client.post(
CHATGPT_OAUTH_TOKEN_URL,
headers={"Content-Type": "application/x-www-form-urlencoded"},
content=body,
)
resp.raise_for_status()
data: Final = resp.json()
except httpx.HTTPStatusError as exc:
raise GetAccessTokenError(
message=f"Token exchange failed: {exc}",
status_code=exc.response.status_code,
)
except Exception as exc:
raise GetAccessTokenError(
message=f"Token exchange failed: {exc}",
status_code=400,
)
if not all(key in data for key in ("access_token", "refresh_token", "id_token")):
raise GetAccessTokenError(
message=f"Token exchange response missing fields: {data}",
status_code=400,
)
return {
"access_token": data["access_token"],
"refresh_token": data["refresh_token"],
"id_token": data["id_token"],View on GitHub (pinned to 6c2dcb801b)
Solutions
- Retry the full device login from the start — a failed exchange invalidates the code.
- Upgrade litellm so the PKCE and redirect-uri construction matches the current OpenAI contract.
- Check system clock skew (NTP) — expired-code 400s are often time-related.
- Consult the propagated HTTP status and OpenAI status page for server-side causes.
Defensive patterns
Strategy: retry
Try / catch
try:
tokens = authenticator._exchange_code_for_tokens(code_data)
except GetAccessTokenError as e:
if e.status_code >= 500:
retry_login_flow() # server-side: retry whole flow
else:
retry_login_flow() # 4xx: code is spent/invalid; fresh flow is the only option Prevention
- Codes are single-use: any exchange failure means start the flow over.
- Keep system clocks NTP-synced to avoid premature code expiry.
- Upgrade litellm so PKCE parameters match the current contract.
When it happens
Trigger: The token exchange POST returns 400 (invalid/expired authorization code, PKCE `code_verifier` mismatch), 401, or 5xx. The upstream HTTP status is propagated into the exception's status_code.
Common situations: Clock skew or delays between approval and exchange expiring the code; a modified/old litellm whose `code_verifier` handling or `redirect_uri` no longer matches OpenAI's expectations; auth server incidents.
Related errors
- Failed to request device code: {exc}
- Polling failed: {exc}
- Timed out waiting for device authorization
- Token exchange response missing fields: {data}
- Refresh token failed: {exc}
AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15).
Data as JSON: /api/errors/135d140d1ef1fc5b.
Report an issue: GitHub.