BerriAI/litellm · error · XAIOAuthError
xAI OAuth token response missing refresh_token
Error message
xAI OAuth token response missing refresh_token
What it means
Raised as XAIOAuthError by XAIOAuthAuthenticator._build_auth_record when an xAI OAuth token exchange (login or refresh) returns a payload with no refresh_token field and no fallback_refresh_token was passed. LiteLLM refuses to build the auth record because every future silent refresh depends on the refresh_token, so storing a record without one would just defer the failure.
Source
Thrown at litellm/llms/xai/oauth.py:341
body: Final = response.json()
except ValueError as exc:
raise XAIOAuthError("xAI OAuth token response was not valid JSON") from exc
if not isinstance(body, dict):
raise XAIOAuthError("xAI OAuth token response was not an object")
return body
def _build_auth_record(
self,
token_payload: dict[str, Any],
token_endpoint: str,
fallback_refresh_token: str | None = None,
) -> dict[str, Any]:
access_token: Final = token_payload.get("access_token")
refresh_token: Final = token_payload.get("refresh_token") or fallback_refresh_token
if not access_token:
raise XAIOAuthError("xAI OAuth token response missing access_token")
if not refresh_token:
raise XAIOAuthError("xAI OAuth token response missing refresh_token")
expires_in: Final = token_payload.get("expires_in") or 3600
try:
expires_at = int(time.time() + int(expires_in))
except (TypeError, ValueError):
expires_at = int(time.time() + 3600)
return {
"access_token": access_token,
"refresh_token": refresh_token,
"id_token": token_payload.get("id_token"),
"token_type": token_payload.get("token_type") or "Bearer",
"token_endpoint": token_endpoint,
"expires_at": expires_at,
}
def _refresh_tokens(self, auth_data: dict[str, Any]) -> dict[str, Any]:
token_endpoint = auth_data.get("token_endpoint")
if not token_endpoint:
token_endpoint = self._discover()["token_endpoint"]View on GitHub (pinned to 77b7c6c40c)
Solutions
- Re-run `litellm xai-oauth login` to start a fresh authorization-code flow that mints a full token set
- Verify the xAI OAuth client requests the offline access / refresh scope so the token endpoint returns refresh_token
- When refreshing, keep the previously stored refresh token so _build_auth_record can fall back to it
- Enable verbose logging to inspect the raw token response and compare it with xAI's current OAuth docs; file a litellm issue if xAI changed the payload
Example fix
# before: auth record built from a payload without refresh_token
auth = xai_auth._build_auth_record(token_payload, token_endpoint) # XAIOAuthError
# after: pass the stored token as fallback, or re-login for a fresh token set
auth = xai_auth._build_auth_record(token_payload, token_endpoint, fallback_refresh_token=stored.get('refresh_token'))
# shell: litellm xai-oauth login Defensive patterns
Strategy: try-catch
Validate before calling
import os, json
# before enabling OAuth, confirm the stored auth record is complete
if os.path.exists(auth_path):
rec = json.load(open(auth_path))
if not rec.get('refresh_token'):
raise SystemExit('Run `litellm xai-oauth login` first: stored record cannot refresh') Type guard
def has_refresh_token(payload: dict) -> bool:
return isinstance(payload, dict) and bool(payload.get('refresh_token')) Try / catch
from litellm.llms.xai.oauth import XAIOAuthError, XAIOAuthLoginRequiredError
try:
token = XAIOAuthAuthenticator().get_access_token()
except XAIOAuthLoginRequiredError:
raise RuntimeError('xAI OAuth re-login required: run `litellm xai-oauth login`')
except XAIOAuthError as exc:
if 'refresh_token' in str(exc):
# token endpoint refused to mint a refresh token; surface scope problem
raise RuntimeError(f'xAI OAuth misconfigured (no refresh_token): {exc}') from exc
raise Prevention
- Complete `litellm xai-oauth login` once per environment before setting use_xai_oauth=True
- Ensure the xAI OAuth client requests offline access so refresh tokens are issued
- Treat the stored auth file as stateful: do not edit, truncate, or partially copy it between machines
- Wrap OAuth token acquisition in one helper with error handling instead of scattering raw calls
When it happens
Trigger: Running `litellm xai-oauth login` (or an automatic refresh) where the xAI token endpoint response omits refresh_token -- typically because the authorization request did not ask for offline access -- or building an auth record from a hand-crafted token_payload dict that lacks the key.
Common situations: xAI OAuth app registered without the offline-access/refresh scope; reusing an already-consumed authorization code (some servers then omit refresh_token); xAI changing its token payload shape; test fixtures with incomplete token payloads.
Related errors
- XAI API key is required. Set api_key, litellm.xai_key, litel
- Cloudflare Exception - {original_exception.message}
- CohereException - {original_exception.message}
- OAuth M2M token request failed: {response.text}
- GigaChat credentials not provided. Set GIGACHAT_CREDENTIALS
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/ba07f619baf7a101.
Report an issue: GitHub.