BerriAI/litellm · error · HTTPException

byok_auth_required

byok_auth_required

Error message

User identity is required for BYOK servers

What it means

A BYOK (bring-your-own-key) MCP server executes each user's tools with that user's own stored credential. Before dispatch, LiteLLM requires a user identity on the request; if user_api_key_auth.user_id is empty, it raises HTTP 401 with error code byok_auth_required and a WWW-Authenticate header pointing at /.well-known/oauth-protected-resource so standards-compliant MCP clients can start the OAuth flow.

Source

Thrown at litellm/proxy/_experimental/mcp_server/server.py:2554

        _write_byok_cred_cache(user_id, mcp_server.server_id, credential)
        return credential

    async def _check_byok_credential(
        mcp_server: MCPServer,
        user_api_key_auth: UserAPIKeyAuth | None,
    ) -> None:
        """
        If the MCP server is BYOK-enabled, verify that the requesting user has a
        stored credential.  When no credential is found, raise an HTTP 401 with a
        WWW-Authenticate header that points the MCP client to our OAuth metadata
        endpoint so it can drive the authorization flow.
        """
        if not mcp_server.is_byok:
            return

        user_id: Final = (user_api_key_auth.user_id if user_api_key_auth else None) or ""
        if not user_id:
            raise HTTPException(
                status_code=401,
                detail={
                    "error": "byok_auth_required",
                    "server_id": mcp_server.server_id,
                    "server_name": mcp_server.server_name or mcp_server.name,
                    "message": "User identity is required for BYOK servers",
                },
                headers={"WWW-Authenticate": 'Bearer resource_metadata="/.well-known/oauth-protected-resource"'},
            )

        # Check shared credential cache before hitting the DB.
        cache_key: Final = (user_id, mcp_server.server_id)
        cached: Final = _byok_cred_cache.get(cache_key)
        if cached is not None:
            cached_cred, ts = cached
            if time.monotonic() - ts < _BYOK_CRED_CACHE_TTL:
                if cached_cred is None:
                    raise HTTPException(

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Attach the key to a user: set user_id when creating the virtual key, or edit the key in the admin UI.
  2. Call with a personal key that already carries a user_id.
  3. If the deployment is intentionally anonymous, do not mark the server is_byok — use a server-level credential instead.
Defensive patterns

Strategy: validation

Validate before calling

async def key_has_user(client: httpx.AsyncClient) -> bool:
    info = (await client.get(f"{base}/key/info")).json()
    return bool(info.get("key_info", {}).get("user_id"))

if not await key_has_user(client):
    raise PermissionError("BYOK servers require a key bound to a user")

Type guard

def is_byok_auth_required_response(resp_json: dict) -> bool:
    d = resp_json.get("detail", {})
    return isinstance(d, dict) and d.get("error") == "byok_auth_required"

Try / catch

except httpx.HTTPStatusError as e:
    if e.response.status_code == 401:
        d = e.response.json().get("detail", {})
        if isinstance(d, dict) and d.get("error") == "byok_auth_required" and "User identity" in str(d.get("message")):
            # rebind the key to a user; retrying with the same key will not help
            raise MissingUserIdentity(server=d.get("server_id")) from e
    raise

Prevention

When it happens

Trigger: Calling a tool on a BYOK server with a service/team virtual key that has no user bound to it; an anonymous session on a public-internet deployment reaching a BYOK tool.

Common situations: CI or service-to-service keys created without an owner; org-level keys reused for MCP calls; scripts using raw keys that were never mapped to a user.

Related errors


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