{"record":{"id":"cc9291e0ebbe1ab7","repo":"BerriAI/litellm","slug":"refresh-response-missing-fields-data","errorCode":null,"errorMessage":"Refresh response missing fields: {data}","messagePattern":"Refresh response missing fields: (.+?)","errorType":"exception","errorClass":"RefreshAccessTokenError","httpStatus":400,"severity":"error","filePath":"litellm/llms/chatgpt/authenticator.py","lineNumber":316,"sourceCode":"                },\n            )\n            resp.raise_for_status()\n            data: Final = resp.json()\n        except httpx.HTTPStatusError as exc:\n            raise RefreshAccessTokenError(\n                message=f\"Refresh token failed: {exc}\",\n                status_code=exc.response.status_code,\n            )\n        except Exception as exc:\n            raise RefreshAccessTokenError(\n                message=f\"Refresh token failed: {exc}\",\n                status_code=400,\n            )\n\n        access_token: Final = data.get(\"access_token\")\n        id_token: Final = data.get(\"id_token\")\n        if not access_token or not id_token:\n            raise RefreshAccessTokenError(\n                message=f\"Refresh response missing fields: {data}\",\n                status_code=400,\n            )\n\n        refreshed: Final = {\n            \"access_token\": access_token,\n            \"refresh_token\": data.get(\"refresh_token\", refresh_token),\n            \"id_token\": id_token,\n        }\n        auth_data: Final = self._build_auth_record(refreshed)\n        self._write_auth_file(auth_data)\n        return refreshed\n\n    def _build_auth_record(self, tokens: dict[str, str]) -> dict[str, Any]:\n        access_token: Final = tokens.get(\"access_token\")\n        id_token: Final = tokens.get(\"id_token\")\n        expires_at: Final = self._get_expires_at(access_token) if access_token else None\n        account_id: Final = self._extract_account_id(id_token or access_token)","sourceCodeStart":298,"sourceCodeEnd":334,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/llms/chatgpt/authenticator.py#L298-L334","documentation":"Raised as `RefreshAccessTokenError` (status 400) when the refresh endpoint returns HTTP 200 but the JSON lacks `access_token` or `id_token`. The server returned an unexpected payload (often an OAuth error object with 200); the raw body is included in the message.","triggerScenarios":"Token refresh where OpenAI responds 200 with `{\"error\": \"invalid_grant\", ...}` or a partial payload missing the two required keys. Common when the refresh token was single-use and already consumed elsewhere.","commonSituations":"Two processes sharing one auth file: the first refresh rotates the token, the second sends the now-consumed token and gets an error body; OpenAI auth API contract drift; old litellm versions.","solutions":["Inspect the response body in the error message — an embedded `error` (e.g. `invalid_grant`) means the stored refresh token is dead; re-login.","Give each deployment/process its own auth file to prevent refresh-token rotation races.","Upgrade litellm for current refresh handling.","Add monitoring on this error to detect forced logouts early."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    resp = litellm.completion(model=\"chatgpt/gpt-4o\", messages=msgs)\nexcept litellm.AuthenticationError as e:\n    if \"Refresh response missing fields\" in str(e) or \"invalid_grant\" in str(e):\n        force_relogin()  # stored refresh token is consumed/dead\n    raise","preventionTips":["Never share one auth file across processes or machines.","Treat `invalid_grant` in the body as mandatory re-login, not retry.","Upgrade litellm to track refresh-payload contract changes."],"tags":["chatgpt","oauth","refresh-token","api-contract","token-rotation"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}