BerriAI/litellm · error · HTTPException
User not found. Only PROXY_ADMIN can create users via /user/
Error message
User not found. Only PROXY_ADMIN can create users via /user/update; use /user/new instead.
What it means
POST /user/update is update-only for non-admins: if no existing row matches the supplied user_id/user_email and the caller is not proxy_admin, LiteLLM returns 404 'User not found. Only PROXY_ADMIN can create users via /user/update; use /user/new instead.' This silent-create guard stops non-admins from creating rows through the update path; proxy admins continue into the update flow instead.
Source
Thrown at litellm/proxy/management_endpoints/internal_user_endpoints.py:1274
user_api_key_dict: UserAPIKeyAuth,
existing_user_row: BaseModel | None,
) -> None:
"""Authorization checks for /user/update — raises HTTPException on failure."""
if user_request.user_role is not None and user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN.value:
raise HTTPException(status_code=403, detail="Only proxy admins can modify user roles.")
if existing_user_row is not None:
typed_row: Final = LiteLLM_UserTable.model_validate(existing_user_row.model_dump(exclude_none=True))
if not can_user_call_user_update(user_api_key_dict=user_api_key_dict, user_info=typed_row):
raise HTTPException(
status_code=403,
detail={
"error": "User does not have permission to update this user. Only PROXY_ADMIN can update other users."
},
)
elif user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN.value:
# Silent-create guard: only PROXY_ADMIN may create via /user/update.
raise HTTPException(
status_code=404,
detail={
"error": "User not found. Only PROXY_ADMIN can create users via /user/update; use /user/new instead."
},
)
async def _invalidate_user_spend_counter_if_changed(
non_default_values: Mapping[str, object],
) -> None:
"""Invalidate the cross-pod spend counter after a direct ``spend`` change.
A direct ``spend`` change must also invalidate the cross-pod spend counter
enforcement reads; the DB write alone leaves a warm counter at the stale
value. ``non_default_values["user_id"]`` is populated in every branch of the
caller (incl. the email-new-user insert path, whose response is a bare model
and not safely subscriptable).
"""View on GitHub (pinned to 77b7c6c40c)
Solutions
- Create the user first with POST /user/new, then apply the update
- Fix the user_id/user_email value (verify against GET /user/list)
- Use a proxy_admin key if the admin update path (which allows more) is genuinely intended
Example fix
# before
POST /user/update -H 'Authorization: Bearer sk-user' {"user_id": "u-typo", "user_alias": "x"} # 404
# after
POST /user/new -H 'Authorization: Bearer sk-user' {"user_id": "u-typo"}
POST /user/update -H 'Authorization: Bearer sk-user' {"user_id": "u-typo", "user_alias": "x"} # 200 Defensive patterns
Strategy: validation
Validate before calling
import requests
def ensure_exists_or_create(base_url: str, headers: dict, user_id: str) -> None:
r = requests.get(f"{base_url}/user/list", headers=headers, timeout=10)
r.raise_for_status()
if not any(u.get("user_id") == user_id for u in r.json().get("data", [])):
requests.post(f"{base_url}/user/new", json={"user_id": user_id}, headers=headers, timeout=10).raise_for_status() Try / catch
except requests.HTTPError as e:
if e.response is not None and e.response.status_code == 404 and "use /user/new" in e.response.text:
requests.post(f"{BASE}/user/new", json={"user_id": payload["user_id"]}, headers=hdrs).raise_for_status()
return retry_update(payload)
raise Prevention
- Treat /user/update as update-only: create with /user/new first
- Validate ids against /user/list before editing
- Avoid upsert assumptions in client code - only PROXY_ADMIN can implicitly upsert
When it happens
Trigger: Non-admin POST /user/update for a user_id or user_email that has no row in LiteLLM_UserTable - a typo, a deleted user, or an attempt to create via update.
Common situations: Upsert-style client code that assumed /user/update creates missing users; typo'd emails; the target user deleted by a cleanup job while an edit was in flight.
Understand the failure class
Background: "User not found", "Invalid user", and "does not exist": what missing-user lookup errors mean across Rocket.Chat, LiteLLM, Phabricator, rustfs, and pnpm — this error's family across 10 libraries.
Related errors
- f"User {user_id} not found"
- LiteLLM Managed File object with id={file_id} not found
- 404
- Organization doesn't exist in db. Organization={org_id}. Cre
- User={valid_token.user_id} has been deactivated via SCIM. Ke
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/ca10a024281f8c1c.
Report an issue: GitHub.