BerriAI/litellm · error · GetAccessTokenError
Token exchange failed
Error message
Token exchange failed: {exc} What it means
GetAccessTokenError raised in the final step of the ChatGPT device flow: the POST to CHATGPT_OAUTH_TOKEN_URL exchanging the authorization code for tokens returned an HTTP error status (raise_for_status fired). The upstream status code is attached and the httpx error text is embedded, so the exact failure (e.g. 400 invalid_grant, 401 bad client) is visible in the message.
Solutions
- Restart the login flow to obtain a fresh authorization code and complete it without pauses
- Upgrade litellm so CHATGPT_CLIENT_ID and the redirect URI match the current OAuth app registration
- Read the embedded status/text: 400 invalid_grant means stale code, 5xx means retry later
- Avoid running two device logins concurrently, which can race the code exchange
Defensive patterns
Strategy: try-catch
Try / catch
from litellm.llms.chatgpt.authenticator import GetAccessTokenError
try:
tokens = authenticator.login()
except GetAccessTokenError as e:
if "Token exchange failed" in str(e) and e.status_code in (400, 401):
print("Authorization code rejected (stale or app mismatch). Restart login; upgrade litellm if it repeats.")
elif e.status_code >= 500:
time.sleep(10)
tokens = authenticator.login()
else:
raise Prevention
- Complete approval promptly so the one-time code does not expire before exchange
- Never retry the exchange step alone; always restart the full device flow
- Keep litellm current so client_id/redirect stay aligned with the registered OAuth app
When it happens
Trigger: Exchanging the one-time authorization code at the token endpoint after the user approved; the code is expired/reused (400), the client_id/redirect mismatch (400/401), or the token endpoint is erroring (5xx).
Common situations: Delays between approval and exchange letting the short-lived code expire; retrying the flow mid-exchange so the code is consumed twice; litellm version whose client_id or redirect_uri no longer matches OpenAI's registered app.
Related errors
- Failed to request device code
- Token exchange response missing fields
- Device code response missing fields
- Polling failed
- Timed out waiting for device authorization
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/135d140d1ef1fc5b.
Report an issue: GitHub.
Appendix: 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 77b7c6c40c)