BerriAI/litellm · error · AzureOpenAIError
Azure AD Token expires_in not returned
Error message
Azure AD Token expires_in not returned
What it means
The token endpoint returned 200 with an access_token but no expires_in field. LiteLLM needs expires_in as the TTL for the in-process azure_ad_cache entry, so it raises 422 rather than guessing a lifetime. Like 1023, this indicates a nonstandard token endpoint or intermediary answered.
Source
Thrown at litellm/llms/azure/common_utils.py:248
"client_assertion": oidc_token,
},
)
if req_token.status_code != 200:
raise AzureOpenAIError(
status_code=req_token.status_code,
message=req_token.text,
)
azure_ad_token_json: Final[_AzureAdTokenJson] = req_token.json()
azure_ad_token_access_token = azure_ad_token_json.get("access_token", None)
azure_ad_token_expires_in: Final = azure_ad_token_json.get("expires_in", None)
if azure_ad_token_access_token is None:
raise AzureOpenAIError(status_code=422, message="Azure AD Token access_token not returned")
if azure_ad_token_expires_in is None:
raise AzureOpenAIError(status_code=422, message="Azure AD Token expires_in not returned")
azure_ad_cache.set_cache(
key=azure_ad_token_cache_key,
value=azure_ad_token_access_token,
ttl=azure_ad_token_expires_in,
)
return azure_ad_token_access_token
def select_azure_base_url_or_endpoint(azure_client_params: dict):
azure_endpoint: Final = azure_client_params.get("azure_endpoint", None)
if azure_endpoint is not None:
# see : https://github.com/openai/openai-python/blob/3d61ed42aba652b547029095a7eb269ad4e1e957/src/openai/lib/azure.py#L192
if "/openai/deployments" in azure_endpoint:
# this is base_url, not an azure_endpoint
azure_client_params["base_url"] = azure_endpoint
azure_client_params.pop("azure_endpoint")View on GitHub (pinned to 6c2dcb801b)
Solutions
- Ensure the endpoint you hit is the real v2.0 endpoint: {authority}/{tenant}/oauth2/v2.0/token always returns expires_in.
- If you operate a broker/mock in the middle, include expires_in (seconds) in its JSON response.
- Check that no proxy is mangling the response body (compare curl output with the app's view).
Example fix
# before
{ "access_token": "eyJ..." }
# after
{ "access_token": "eyJ...", "expires_in": 3599 } Defensive patterns
Strategy: validation
Try / catch
try:
resp = litellm.completion(..., azure_ad_token=oidc)
except AzureOpenAIError as e:
if e.status_code == 422 and "expires_in" in str(e):
log.error("nonstandard token endpoint: response lacked expires_in")
raise Prevention
- Only use real Azure authorities for token exchange in production.
- If you run a token broker, contract-test that it returns access_token, expires_in, token_type.
- Avoid adal v1 endpoints; use the v2.0 endpoint LiteLLM targets.
When it happens
Trigger: A custom token broker or mock returning {access_token: ...} without expiry; an API gateway rewriting the response body; OAuth-compliant servers that (rarely) omit expires_in when a default is assumed.
Common situations: Internal token-broker services standing in for Azure AD; contract tests with hand-written fixtures; older ADAL endpoints returning non-v2 payloads.
Related errors
- Azure AD Token access_token not returned
- {req_token.text}
- AZURE_SENTINEL_DCR_IMMUTABLE_ID is required. Set it as an en
- AZURE_SENTINEL_ENDPOINT is required. Set it as an environmen
- AZURE_SENTINEL_TENANT_ID or AZURE_TENANT_ID is required. Set
AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15).
Data as JSON: /api/errors/3b791d88ebf7ae38.
Report an issue: GitHub.