BerriAI/litellm · error · UnauthorizedError

{e}

Error message

{e}

What it means

Raised by CredentialsManagementClient.list() when GET {base_url}/credentials returns HTTP 401. The client maps every 401 to UnauthorizedError (litellm/proxy/client/exceptions.py); its message is the secret-redacted str of the underlying requests HTTPError (e.g. '401 Client Error: Unauthorized for url: http://localhost:4000/credentials') and the original error is kept on .orig_exception. /credentials is an admin endpoint, so 401 means the request carried no acceptable Bearer key — note the client sends no Authorization header at all when api_key is None (Client only falls back to the stored CLI key when it was issued for the same base_url).

Source

Thrown at litellm/proxy/client/credentials.py:64

        Raises:
            UnauthorizedError: If the request fails with a 401 status code
            requests.exceptions.RequestException: If the request fails with any other error
        """
        url: Final = f"{self._base_url}/credentials"

        request: Final = requests.Request("GET", url, headers=self._get_headers())

        if return_request:
            return request

        session: Final = requests.Session()
        try:
            response: Final = session.send(request.prepare())
            response.raise_for_status()
            return response.json()
        except requests.exceptions.HTTPError as e:
            if e.response.status_code == 401:
                raise UnauthorizedError(e)
            raise

    def create(
        self,
        credential_name: str,
        credential_info: dict[str, Any],
        credential_values: dict[str, Any],
        return_request: bool = False,
    ) -> dict[str, Any] | requests.Request:
        """
        Create a new credential.

        Args:
            credential_name (str): Name of the credential
            credential_info (Dict[str, Any]): Additional information about the credential
            credential_values (Dict[str, Any]): Values for the credential
            return_request (bool): If True, returns the prepared request object instead of executing it

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Pass an admin key explicitly: CredentialsManagementClient(base_url, api_key=os.environ["LITELLM_MASTER_KEY"]).list() — or Client(base_url, api_key=...).credentials.list()
  2. Verify the key out-of-band: curl -H "Authorization: Bearer $KEY" $BASE_URL/credentials should return 200, not 401
  3. If the key was revoked/expired, mint a new admin key on the proxy and update the client configuration
  4. Double-check base_url scheme/host/port — a valid key for a different server still yields 401 here

Example fix

# before
from litellm.proxy.client.credentials import CredentialsManagementClient
client = CredentialsManagementClient("http://localhost:4000")
creds = client.credentials.list() if hasattr(client, "credentials") else client.list()  # UnauthorizedError

# after
import os
from litellm.proxy.client.credentials import CredentialsManagementClient
client = CredentialsManagementClient("http://localhost:4000", api_key=os.environ["LITELLM_MASTER_KEY"])
creds = client.list()
Defensive patterns

Strategy: try-catch

Validate before calling

import os, requests

def assert_credentials_access(base_url: str, api_key: str | None) -> None:
    if not api_key:
        raise ValueError("api_key is required — LiteLLM /credentials is admin-only")
    r = requests.get(
        f"{base_url.rstrip('/')}/credentials",
        headers={"Authorization": f"Bearer {api_key}"},
        timeout=10,
    )
    if r.status_code == 401:
        raise ValueError("proxy rejected the key (401) — check/rotate the admin key")

Try / catch

from litellm.proxy.client.exceptions import UnauthorizedError
import requests

try:
    creds = client.list()
except UnauthorizedError as e:
    raise RuntimeError(f"credentials rejected (401): {e}") from e  # fix key; do not blind-retry
except requests.exceptions.HTTPError as e:
    status = e.response.status_code if e.response is not None else None
    handle_other(status, e)  # 403 = wrong role, 404/500 = server-side

Prevention

When it happens

Trigger: Constructing the client without api_key against an auth-enforcing proxy (header omitted entirely); passing a virtual/end-user key that lacks admin scope instead of the master key; using a key that was deleted, revoked, expired, or rotated on the proxy.

Common situations: Proxy started with a master_key in config but the script reads the wrong env var (empty string also omits the header); key rotated server-side while hardcoded in client code; base_url pointing at a different environment (staging key vs prod proxy); typos in the key string.

Related errors


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/d3f08ff812f9b0ff. Report an issue: GitHub.