{"record":{"id":"382c5e6d8fa4a39e","repo":"BerriAI/litellm","slug":"e-382c5e","errorCode":null,"errorMessage":"{e}","messagePattern":"\\{e\\}","errorType":"exception","errorClass":"UnauthorizedError","httpStatus":401,"severity":"error","filePath":"litellm/proxy/client/keys.py","lineNumber":107,"sourceCode":"            params[\"key_alias\"] = key_alias\n        if return_full_object is not None:\n            params[\"return_full_object\"] = str(return_full_object).lower()\n        if include_team_keys is not None:\n            params[\"include_team_keys\"] = str(include_team_keys).lower()\n\n        request: Final = requests.Request(\"GET\", url, headers=self._get_headers(), params=params)\n\n        if return_request:\n            return request\n\n        session: Final = requests.Session()\n        try:\n            response: Final = session.send(request.prepare())\n            response.raise_for_status()\n            return response.json()\n        except requests.exceptions.HTTPError as e:\n            if e.response.status_code == 401:\n                raise UnauthorizedError(e)\n            raise\n\n    def generate(\n        self,\n        models: builtins.list[str] | None = None,\n        aliases: dict[str, str] | None = None,\n        spend: float | None = None,\n        duration: str | None = None,\n        key_alias: str | None = None,\n        team_id: str | None = None,\n        user_id: str | None = None,\n        budget_id: str | None = None,\n        config: dict[str, Any] | None = None,\n        return_request: bool = False,\n    ) -> dict[str, Any] | requests.Request:\n        \"\"\"\n        Generate an API key based on the provided data.\n","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/client/keys.py#L89-L125","documentation":"Raised by KeysManagementClient.list() when GET {base_url}/key/list (with whatever of page/size/user_id/team_id/organization_id/key_hash/key_alias/return_full_object/include_team_keys you passed) returns HTTP 401. The client converts 401 to UnauthorizedError (message is the redacted requests HTTPError text, original kept on .orig_exception); every other non-2xx — including 403 insufficient-role — re-raises as a plain requests HTTPError. Key listing is an admin-tier operation, so end-user keys are rejected.","triggerScenarios":"Listing keys with no api_key configured (no Authorization header sent) against an auth-enforcing proxy; using a virtual key that lacks permission to list keys; a key that was deleted or expired server-side.","commonSituations":"Reporting scripts authenticating with a team member's key instead of the master key; proxy hardened with a master_key after the script was written; rotated keys not propagated to configuration.","solutions":["Pass an admin-capable key: KeysManagementClient(base_url, api_key=os.environ[\"LITELLM_MASTER_KEY\"]).list()","Confirm with curl: curl -H \"Authorization: Bearer $KEY\" \"$BASE_URL/key/list?page=1&size=10\"","If only a 403 appears after fixing auth, grant the key list-permissions or use the master key","Reissue/rotate the key if the proxy no longer recognizes it"],"exampleFix":"# before\nfrom litellm.proxy.client.keys import KeysManagementClient\nkeys = KeysManagementClient(\"http://localhost:4000\")\nkeys.list()  # UnauthorizedError\n\n# after\nimport os\nkeys = KeysManagementClient(\"http://localhost:4000\", api_key=os.environ[\"LITELLM_MASTER_KEY\"])\nkeys.list()","handlingStrategy":"try-catch","validationCode":"import requests\n\ndef can_list_keys(base_url: str, api_key: str | None) -> bool:\n    if not api_key:\n        return False\n    r = requests.get(\n        f\"{base_url.rstrip('/')}/key/list\",\n        headers={\"Authorization\": f\"Bearer {api_key}\"},\n        params={\"page\": 1, \"size\": 1},\n        timeout=10,\n    )\n    return r.status_code != 401","typeGuard":null,"tryCatchPattern":"from litellm.proxy.client.exceptions import UnauthorizedError\nimport requests\n\ntry:\n    page = keys.list(page=1, size=50)\nexcept UnauthorizedError:\n    rotate_credentials()  # 401: wrong/absent/revoked key — no point retrying\nexcept requests.exceptions.HTTPError as e:\n    if e.response is not None and e.response.status_code == 403:\n        grant_list_permission_or_use_master_key()\n    raise","preventionTips":["Use the master/admin key for /key/list; virtual keys need explicit list permissions","Check the resolved api_key is non-empty before building management clients","Distinguish 403 (authenticated but unprivileged) from 401 in your handler — only the former maps to UnauthorizedError"],"tags":["litellm","authentication","http-401","python","virtual-keys"],"backgroundTag":"http-401-unauthorized","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}