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

  1. Ensure the endpoint you hit is the real v2.0 endpoint: {authority}/{tenant}/oauth2/v2.0/token always returns expires_in.
  2. If you operate a broker/mock in the middle, include expires_in (seconds) in its JSON response.
  3. 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

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


AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15). Data as JSON: /api/errors/3b791d88ebf7ae38. Report an issue: GitHub.