BerriAI/litellm · error · Exception
OIDC UserInfo endpoint returned status {response.status_code
Error message
OIDC UserInfo endpoint returned status {response.status_code}: {response.text} What it means
Raised in get_userinfo when the OIDC UserInfo endpoint answers with a non-200 status. The response body is included in the message, so the IdP's own error text (e.g. 'invalid token', 'insufficient scope') is visible. Successful responses are cached per-token-hash for oidc_userinfo_cache_ttl, so this only fires on cache misses.
Source
Thrown at litellm/proxy/auth/handle_jwt.py:767
if cached_userinfo is not None:
verbose_proxy_logger.debug("Returning cached OIDC UserInfo")
return cached_userinfo
verbose_proxy_logger.debug("Calling OIDC UserInfo endpoint: %s", self.litellm_jwtauth.oidc_userinfo_endpoint)
try:
# Call the UserInfo endpoint with the access token
response: Final = await self.http_handler.get(
url=self.litellm_jwtauth.oidc_userinfo_endpoint,
headers={
"Authorization": f"Bearer {token}",
"Accept": "application/json",
},
)
if response.status_code != 200:
raise Exception(f"OIDC UserInfo endpoint returned status {response.status_code}: {response.text}")
userinfo: Final = response.json()
verbose_proxy_logger.debug("Received OIDC UserInfo: %s", userinfo)
# Cache the userinfo response
await self.user_api_key_cache.async_set_cache(
key=cache_key,
value=userinfo,
ttl=self.litellm_jwtauth.oidc_userinfo_cache_ttl,
)
return userinfo
except Exception as e:
verbose_proxy_logger.error("Error fetching OIDC UserInfo: %s", e)
raise Exception(f"Failed to fetch OIDC UserInfo: {e}")
_unscoped_jwt_warning_emitted = FalseView on GitHub (pinned to 77b7c6c40c)
Solutions
- Reproduce manually: curl -H 'Authorization: Bearer <token>' <userinfo_endpoint> and read the IdP's error body included in the message
- If the token is expired or revoked, obtain a fresh access token and retry
- Ensure the requested scopes include openid when the token is minted
- Confirm oidc_userinfo_endpoint matches the realm/tenant that issued the token
Defensive patterns
Strategy: validation
Validate before calling
import time, jwt as pyjwt
def token_is_fresh(token: str, skew_seconds: int = 30) -> bool:
payload = pyjwt.decode(token, options={"verify_signature": False})
exp = payload.get("exp")
return exp is None or exp - skew_seconds > time.time() Try / catch
# the IdP's error body is embedded in the message - surface it
t
try:
await proxy_call_with_jwt(token)
except Exception as e:
if "OIDC UserInfo endpoint returned status 401" in str(e):
token = await refresh_access_token() # expired/revoked at the IdP
else:
raise Prevention
- Request the openid scope when minting tokens used for UserInfo
- Refresh access tokens before their exp - UserInfo checks happen at the IdP, not just locally
- Match oidc_userinfo_endpoint to the exact realm/tenant that issued the token
When it happens
Trigger: The proxy calls GET <oidc_userinfo_endpoint> with Authorization: Bearer <access token> and the IdP returns 401 (token expired/revoked or lacking the openid scope), 403 (forbidden), or 5xx - most commonly an expired access token that still passed local JWT verification, or a token without the openid scope.
Common situations: See trigger scenarios.
Related errors
- JWT Auth: OIDC discovery endpoint {url} returned status {res
- OIDC UserInfo endpoint not configured. Set 'oidc_userinfo_en
- Failed to fetch OIDC UserInfo: {e}
- DeepEval logging error: {e.response.text}
- Failed to fetch models from Lemonade. Status code: {response
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/24af8f15a7c7d84a.
Report an issue: GitHub.