BerriAI/litellm · error · HTTPException

Service-account keys cannot query user analytics. Use a user

Error message

Service-account keys cannot query user analytics. Use a user-bound key, or call as a proxy admin.

What it means

require_caller_user_id_for_non_admin backs the non-admin branch of the user analytics endpoints: those endpoints scope queries to the caller's own user_id, but service-account keys are deliberately created with user_id=None (key_management_endpoints.py forces data.user_id=None). A None user_id would flow into the daily-activity builder, where entity_id=None means 'no filter' — returning every tenant's data. The guard closes that hole by raising HTTP 403 before the query whenever the non-admin caller has no user_id.

Source

Thrown at litellm/proxy/management_endpoints/common_utils.py:97


def require_caller_user_id_for_non_admin(
    user_api_key_dict: UserAPIKeyAuth,
) -> str:
    """Return the caller's user_id, or raise 403 if missing.

    Non-admin analytics endpoints scope queries by the caller's own user_id.
    Service-account keys are deliberately created with user_id=None
    (key_management_endpoints.py forces ``data.user_id = None`` at key
    creation). Without this guard, that None value flows through to the
    daily-activity builder, which treats ``entity_id is None`` as "no filter"
    and returns every tenant's data.

    Callers must check is_admin first; this helper is only valid on the
    non-admin scoping branch.
    """
    if user_api_key_dict.user_id is None:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail={
                "error": (
                    "Service-account keys cannot query user analytics. Use a user-bound key, or call as a proxy admin."
                )
            },
        )
    return user_api_key_dict.user_id


def _check_passthrough_routes_caller_permission(
    data: BaseModel,
    user_api_key_dict: UserAPIKeyAuth,
    *,
    entity: str = "key",
) -> None:
    """
    Only proxy admins may set `allowed_passthrough_routes` (top-level or under

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Use a key bound to a real user (create the key with a valid user_id) when self-scoped analytics are needed
  2. Or perform the call with an admin/master key, which takes the admin branch and is not subject to this guard
  3. If you own the service account, treat analytics as admin-only and query them from an admin context

Example fix

# before: service-account key (user_id=None)
curl http://localhost:4000/user/info -H "Authorization: Bearer sk-service"  # 403 Service-account keys cannot query user analytics

# after: user-bound key or master key
curl http://localhost:4000/user/info -H "Authorization: Bearer sk-user-bound"
curl http://localhost:4000/user/info -H "Authorization: Bearer $LITELLM_MASTER_KEY"
Defensive patterns

Strategy: validation

Validate before calling

import httpx

r = httpx.get(f"{PROXY_URL}/key/info", params={"key": KEY}, headers=admin_hdrs)
r.raise_for_status()
info = r.json().get("key_info", {})
if info.get("user_id") is None:
    raise PermissionError(
        "this is a service-account key; query analytics with a user-bound or admin key"
    )

Type guard

from typing import TypeGuard

def is_user_bound_key(info: dict) -> TypeGuard[dict]:
    """True when the key's info binds to a real user (analytics-safe)."""
    return isinstance(info.get("user_id"), str) and info["user_id"] != ""

Try / catch

import httpx

try:
    r = httpx.get(f"{PROXY_URL}/user/info", params=analytics_params, headers=hdrs)
    r.raise_for_status()
except httpx.HTTPStatusError as e:
    if e.response.status_code == 403 and "Service-account keys" in e.response.text:
        # correct behavior in multi-tenant setups: switch keys, do not retry
        raise PermissionError("use a user-bound key or call as proxy admin") from e
    raise

Prevention

When it happens

Trigger: Calling user analytics endpoints (e.g. GET /user/info spend/activity routes) with a service-account virtual key (created without a user, or explicitly with NO user binding) while not being a proxy admin.

Common situations: Machine-to-machine integrations using service keys that try to read 'their own' analytics; health checks wired to analytics endpoints with a service key; multi-tenant deployments where the 403 is the correct privacy guarantee.

Understand the failure class

Background: "You do not have permission" / 403 Forbidden errors: authenticated but not allowed — causes and fixes across open-source libraries — this error's family across 31 libraries.

Related errors


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