{"record":{"id":"135d140d1ef1fc5b","repo":"BerriAI/litellm","slug":"token-exchange-failed-exc","errorCode":null,"errorMessage":"Token exchange failed: {exc}","messagePattern":"Token exchange failed: (.+?)","errorType":"exception","errorClass":"GetAccessTokenError","httpStatus":null,"severity":"error","filePath":"litellm/llms/chatgpt/authenticator.py","lineNumber":267,"sourceCode":"        try:\n            client: Final = _get_httpx_client()\n            redirect_uri: Final = f\"{CHATGPT_AUTH_BASE}/deviceauth/callback\"\n            body: Final = (\n                \"grant_type=authorization_code\"\n                f\"&code={code_data['authorization_code']}\"\n                f\"&redirect_uri={redirect_uri}\"\n                f\"&client_id={CHATGPT_CLIENT_ID}\"\n                f\"&code_verifier={code_data['code_verifier']}\"\n            )\n            resp: Final = client.post(\n                CHATGPT_OAUTH_TOKEN_URL,\n                headers={\"Content-Type\": \"application/x-www-form-urlencoded\"},\n                content=body,\n            )\n            resp.raise_for_status()\n            data: Final = resp.json()\n        except httpx.HTTPStatusError as exc:\n            raise GetAccessTokenError(\n                message=f\"Token exchange failed: {exc}\",\n                status_code=exc.response.status_code,\n            )\n        except Exception as exc:\n            raise GetAccessTokenError(\n                message=f\"Token exchange failed: {exc}\",\n                status_code=400,\n            )\n\n        if not all(key in data for key in (\"access_token\", \"refresh_token\", \"id_token\")):\n            raise GetAccessTokenError(\n                message=f\"Token exchange response missing fields: {data}\",\n                status_code=400,\n            )\n        return {\n            \"access_token\": data[\"access_token\"],\n            \"refresh_token\": data[\"refresh_token\"],\n            \"id_token\": data[\"id_token\"],","sourceCodeStart":249,"sourceCodeEnd":285,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/llms/chatgpt/authenticator.py#L249-L285","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    tokens = authenticator._exchange_code_for_tokens(code_data)\nexcept GetAccessTokenError as e:\n    if e.status_code >= 500:\n        retry_login_flow()  # server-side: retry whole flow\n    else:\n        retry_login_flow()  # 4xx: code is spent/invalid; fresh flow is the only option","preventionTips":["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."],"tags":["chatgpt","oauth","token-exchange","pkce","auth"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}