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
- Attach the key to a user: set user_id when creating the virtual key, or edit the key in the admin UI.
- Call with a personal key that already carries a user_id.
- 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
- Bind every virtual key that will touch BYOK servers to a user_id at creation time.
- Audit service/team keys for missing user_id before enabling BYOK servers.
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
- MCPJWTSigner: incoming token verification failed: {exc}
- User ID not found in token
- Authentication failed. Check your Arize Phoenix API key and
- Authentication failed. Check your BitBucket access token and
- challenge.body if challenge.body is not None else error.summ
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/fe0cb9e755969283.
Report an issue: GitHub.