{"record":{"id":"64194c308214574d","repo":"BerriAI/litellm","slug":"token-exchange-response-missing-fields-data","errorCode":null,"errorMessage":"Token exchange response missing fields: {data}","messagePattern":"Token exchange response missing fields: (.+?)","errorType":"exception","errorClass":"GetAccessTokenError","httpStatus":400,"severity":"error","filePath":"litellm/llms/chatgpt/authenticator.py","lineNumber":278,"sourceCode":"                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\"],\n        }\n\n    def _refresh_tokens(self, refresh_token: str) -> dict[str, str]:\n        try:\n            client: Final = _get_httpx_client()\n            resp: Final = client.post(\n                CHATGPT_OAUTH_TOKEN_URL,\n                json={\n                    \"client_id\": CHATGPT_CLIENT_ID,\n                    \"grant_type\": \"refresh_token\",\n                    \"refresh_token\": refresh_token,","sourceCodeStart":260,"sourceCodeEnd":296,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/llms/chatgpt/authenticator.py#L260-L296","documentation":"Raised as `GetAccessTokenError` (status 400) when the token-exchange response is HTTP 200 but missing any of `access_token`, `refresh_token`, or `id_token`. Indicates OpenAI's token endpoint returned an unexpected payload shape (e.g. an error body with 200, or an API change).","triggerScenarios":"Completing device login where the token endpoint returns 200 with a partial or error-shaped JSON (missing the three required keys); the full raw body is embedded in the message for diagnosis.","commonSituations":"litellm lagging behind an OpenAI auth API change; scopes/consent changes causing the server to omit tokens; proxy interference rewriting the response.","solutions":["Read the embedded response body in the message — it usually contains an `error` field explaining the omission.","Upgrade litellm to pick up parsing fixes for the current API contract.","Retry the login from scratch; transient incomplete responses do occur.","If the body shows a consent/scope error, complete the flow in a browser profile with the right account permissions."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    tokens = authenticator._exchange_code_for_tokens(code_data)\nexcept GetAccessTokenError as e:\n    if \"missing fields\" in str(e):\n        log.error(\"token endpoint returned: %s\", e.message)  # contains raw body\n    raise","preventionTips":["Upgrade litellm when OpenAI auth payloads change.","Inspect the embedded body for an `error` field to distinguish consent/scope problems.","Re-login from scratch after any partial exchange failure."],"tags":["chatgpt","oauth","token-exchange","api-contract"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}