BerriAI/litellm · error · DatabricksException

OAuth M2M token request failed: {response.text}

Error message

OAuth M2M token request failed: {response.text}

What it means

Raised when the Databricks OAuth M2M token endpoint responds but with a non-200 status. The exception carries the endpoint's status code and the raw response body (typically an OAuth error like invalid_client), telling you the request reached the server but authentication failed.

Source

Thrown at litellm/llms/databricks/common_utils.py:252

        try:
            response: Final = requests.post(
                token_url,
                data={
                    "grant_type": "client_credentials",
                    "scope": "all-apis",
                },
                auth=(client_id, client_secret),
                headers={"Content-Type": "application/x-www-form-urlencoded"},
                timeout=30,
            )
        except requests.RequestException as e:
            raise DatabricksException(
                status_code=500,
                message=f"OAuth M2M token request failed: {e}",
            )

        if response.status_code != 200:
            raise DatabricksException(
                status_code=response.status_code,
                message=f"OAuth M2M token request failed: {response.text}",
            )

        token_data: Final = response.json()
        return token_data["access_token"]

    def _get_databricks_credentials(
        self, api_key: str | None, api_base: str | None, headers: dict | None
    ) -> tuple[str, dict]:
        """
        Get Databricks credentials using the Databricks SDK.

        Also registers LiteLLM as a partner for proper telemetry attribution
        in Databricks system.access.audit table.

        Args:
            api_key: Optional API key (PAT)

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Read response.text in the message — invalid_client means bad credentials, invalid_scope means permission issues
  2. Regenerate the service principal secret in Databricks and update DATABRICKS_CLIENT_ID/DATABRICKS_CLIENT_SECRET
  3. Strip whitespace from secret values loaded from files/vaults
  4. Confirm the service principal has access to the workspace and serving endpoints

Example fix

# before
client_secret = open("secret.txt").read()  # includes trailing \n

# after
client_secret = open("secret.txt").read().strip()
Defensive patterns

Strategy: try-catch

Validate before calling

client_id = os.getenv("DATABRICKS_CLIENT_ID", "").strip()
client_secret = os.getenv("DATABRICKS_CLIENT_SECRET", "").strip()
assert client_id and client_secret, "OAuth M2M credentials missing/blank"

Try / catch

try:
    resp = litellm.completion(model=m, messages=msgs)
except Exception as e:
    if "invalid_client" in str(e):
        raise ConfigError("Databricks service principal secret is wrong or expired") from e
    raise

Prevention

When it happens

Trigger: Posting client_credentials grant with a bad client_id/client_secret (400 invalid_client), a service principal secret that expired or was rotated, missing 'all-apis' scope permissions, or a workspace where the service principal cannot authenticate.

Common situations: Rotated Databricks service-principal secrets not updated in env vars; OAuth secrets (not PATs) confused with DATABRICKS_API_KEY; secret typos or trailing whitespace/newlines when copied from a vault.

Related errors


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