BerriAI/litellm · error · ValueError

Unable to record skill ownership: caller has no identity sco

Error message

Unable to record skill ownership: caller has no identity scope.

What it means

When creating a skill, the handler derives an owner from get_primary_resource_owner_scope(user_api_key_dict) (user/team/org/api-key identity) falling back to the explicit user_id. If both are absent — an identity-less caller — it raises ValueError rather than storing a placeholder, because a shared fake owner would let anonymous callers read each other's skills. This is a deliberate multi-tenant isolation guard.

Source

Thrown at litellm/llms/litellm_proxy/skills/handler.py:77

        return prisma_client

    @staticmethod
    async def create_skill(
        data: NewSkillRequest,
        user_id: str | None = None,
        user_api_key_dict: UserAPIKeyAuth | None = None,
    ) -> LiteLLM_SkillsTable:
        prisma_client: Final = await LiteLLMSkillsHandler._get_prisma_client()

        skill_id: Final = f"{LITELLM_SKILL_ID_PREFIX}{uuid.uuid4()}"
        owner: Final = get_primary_resource_owner_scope(user_api_key_dict) or user_id
        if owner is None:
            # Identity-less callers (no user_id / team_id / org_id /
            # api_key / token) can't be uniquely stamped on the row.
            # Stamping a placeholder would let any two such callers see
            # each other's skills via the shared owner. ValueError keeps
            # this module FastAPI-free per the project layering rule.
            raise ValueError("Unable to record skill ownership: caller has no identity scope.")

        skill_data: Final[dict[str, Any]] = {
            "skill_id": skill_id,
            "display_title": data.display_title,
            "description": data.description,
            "instructions": data.instructions,
            "source": "custom",
            "created_by": owner,
            "updated_by": owner,
        }

        if data.metadata is not None:
            from litellm.litellm_core_utils.safe_json_dumps import safe_dumps

            skill_data["metadata"] = safe_dumps(data.metadata)

        if data.file_content is not None:
            from prisma.fields import Base64

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Authenticate the request with a valid LiteLLM proxy virtual key (Authorization: Bearer sk-...) so the caller has an identity scope
  2. Ensure the key/user is bound to a user, team, or organization in the proxy so owner resolution succeeds
  3. For programmatic calls to the handler, pass user_id explicitly when no UserAPIKeyAuth scope exists

Example fix

# before
resp = client.post("/v1/skills", json=skill_payload)  # no auth header

# after
resp = client.post(
    "/v1/skills",
    headers={"Authorization": "Bearer sk-my-proxy-key"},
    json=skill_payload,
)
Defensive patterns

Strategy: validation

Validate before calling

def caller_has_identity(user_api_key_dict, user_id: str | None) -> bool:
    scope = None
    if user_api_key_dict is not None:
        scope = getattr(user_api_key_dict, "user_id", None) or getattr(user_api_key_dict, "team_id", None) \
            or getattr(user_api_key_dict, "organization_id", None) or getattr(user_api_key_dict, "api_key", None)
    return bool(scope or user_id)

Try / catch

try:
    skill = await LiteLLMSkillsHandler.create_skill(data=payload, user_api_key_dict=auth, user_id=uid)
except ValueError as e:
    if "no identity scope" in str(e):
        raise HTTPException(status_code=401, detail="Authenticated key required to create skills") from e

Prevention

When it happens

Trigger: POSTing to the skills endpoint without an authenticated identity: missing/invalid API key header so user_api_key_dict carries no scopes, and no user_id supplied; internal/test invocations of create_skill passing neither argument.

Common situations: Calling the skills route with a personal (no team/org binding) key that failed auth parsing; scripts hitting the proxy without the Authorization header; auth middleware disabled in dev so UserAPIKeyAuth is empty.

Related errors


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