Coverage for .venv/lib/python3.13/site-packages/litellm/proxy/management_endpoints/management_v1/users.py: 77%
40 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:01 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:01 +0000
1"""`POST /management/v1/users/bulk` and `POST /management/v1/users/bulk_delete`."""
3from typing import Annotated, Final
5from fastapi import APIRouter, Depends, Header
7from litellm._logging import verbose_proxy_logger
8from litellm.proxy._types import CommonProxyErrors, UserAPIKeyAuth
9from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
10from litellm.proxy.list_api.common import PROBLEM_TYPE_BASE, ManagementProblem, reject_unknown_query_params
11from litellm.proxy.management_endpoints.management_v1.common import MANAGEMENT_V1_PREFIX
12from litellm.proxy.management_helpers.bulk_user_creation import bulk_create_users
13from litellm.proxy.management_helpers.bulk_user_deletion import bulk_delete_users
14from litellm.proxy.management_helpers.utils import (
15 management_endpoint_wrapper, # pyright: ignore[reportUnknownVariableType] # legacy untyped decorator
16)
17from litellm.types.proxy.management_endpoints.internal_user_endpoints import (
18 BulkDeleteUserRequest,
19 BulkDeleteUsersResponse,
20 BulkNewUserRequest,
21 BulkNewUserResponse,
22)
23from litellm.types.proxy.management_endpoints.management_v1 import ProblemDetail
25router: Final = APIRouter(prefix=MANAGEMENT_V1_PREFIX)
28@router.post(
29 "/users/bulk",
30 tags=["Internal User management"], # mutable-ok: fastapi types tags as list[str | Enum]
31 dependencies=(Depends(user_api_key_auth),),
32 response_model=BulkNewUserResponse,
33)
34@management_endpoint_wrapper
35async def bulk_create_users_route(
36 data: BulkNewUserRequest,
37 user_api_key_dict: Annotated[UserAPIKeyAuth, Depends(user_api_key_auth)],
38) -> BulkNewUserResponse:
39 """
40 Create up to 500 internal users in one request, optionally adding each one to teams.
42 Every entry in `users` takes the same fields as `/user/new`, with two differences: `auto_create_key`
43 defaults to `false` (opt in per user to also get a virtual key back) and `send_invite_email` is not
44 supported. Unknown fields are rejected with 422. Rows are validated together (duplicate ids or emails,
45 unknown teams, roles the caller may not grant), inserted in one statement, and each referenced team is
46 written once for all of its new members.
48 Rows fail independently: a bad row is reported in `data` with `success: false` and an `error`, and the
49 other rows still get created. A user that was created but could not be added to one of its teams is
50 reported with `success: true`, `teams` listing where they did land, and `error` naming the failed team.
51 The whole request is refused with a 403 problem document only if creating the valid rows would exceed
52 the license seat limit.
54 Example curl:
55 ```
56 curl -X POST "http://localhost:4000/management/v1/users/bulk" \\
57 -H "Content-Type: application/json" \\
58 -H "Authorization: Bearer sk-1234" \\
59 -d '{
60 "users": [
61 {"user_email": "a@example.com", "user_role": "internal_user", "teams": ["team-1"]},
62 {"user_email": "b@example.com", "user_role": "internal_user", "auto_create_key": true}
63 ]
64 }'
65 ```
67 Returns `data` (one entry per input row, in order, with `user_id`, `user_email`, `success`, `teams`,
68 `key`, `error`) and `meta` with `total_requested`, `created` and `failed`.
69 """
70 try:
71 from litellm.proxy.proxy_server import (
72 _license_check, # pyright: ignore[reportPrivateUsage] # same proxy license singleton /user/new reads
73 litellm_proxy_admin_name,
74 prisma_client,
75 user_api_key_cache,
76 )
78 if prisma_client is None: 78 ↛ 79line 78 didn't jump to line 79 because the condition on line 78 was never true
79 raise ManagementProblem(
80 ProblemDetail(
81 type=f"{PROBLEM_TYPE_BASE}database-not-connected",
82 title="Database not connected",
83 status=503,
84 detail=CommonProxyErrors.db_not_connected_error.value,
85 )
86 )
88 return await bulk_create_users(
89 users=data.users,
90 user_api_key_dict=user_api_key_dict,
91 prisma_client=prisma_client,
92 license_check=_license_check,
93 litellm_proxy_admin_name=litellm_proxy_admin_name,
94 user_api_key_cache=user_api_key_cache,
95 )
97 except ManagementProblem:
98 raise
99 except Exception: # noqa: BLE001 # a driver error answers as a problem document, not the OpenAI error shape
100 verbose_proxy_logger.exception("/management/v1/users/bulk: Exception occurred")
101 raise ManagementProblem(
102 ProblemDetail(
103 type=f"{PROBLEM_TYPE_BASE}internal-server-error",
104 title="Internal server error",
105 status=500,
106 detail="Failed to create users.",
107 )
108 )
111@router.post(
112 "/users/bulk_delete",
113 tags=["Internal User management"], # mutable-ok: FastAPI types `tags` as list[str], not Sequence
114 dependencies=(Depends(user_api_key_auth), Depends(reject_unknown_query_params)),
115 response_model=BulkDeleteUsersResponse,
116)
117@management_endpoint_wrapper
118async def bulk_delete_users_action(
119 data: BulkDeleteUserRequest,
120 user_api_key_dict: Annotated[UserAPIKeyAuth, Depends(user_api_key_auth)],
121 litellm_changed_by: Annotated[
122 str | None,
123 Header(description="Who the caller is acting for; recorded on the audit log entries this call writes."),
124 ] = None,
125) -> BulkDeleteUsersResponse:
126 """
127 Delete up to 500 users in one call, taking each out of every team it belongs to.
128 Same authorization as `/user/delete`: proxy admins may delete anyone, org admins
129 only users inside organizations they administer. Unknown body fields are a 422.
131 `data` holds one result per requested `user_id`, in request order. A row is
132 `success: false` with an `error` when the id is unknown, repeated in the request,
133 or outside the caller's scope. Rows that pass those checks are deleted together,
134 in one transaction, so either all of them go or none does.
136 Example curl:
137 ```
138 curl --location 'http://0.0.0.0:4000/management/v1/users/bulk_delete' \
139 --header 'Authorization: Bearer sk-1234' \
140 --header 'Content-Type: application/json' \
141 --data '{"user_ids": ["user-1", "user-2"]}'
142 ```
143 """
144 try:
145 from litellm.proxy.proxy_server import (
146 litellm_proxy_admin_name,
147 prisma_client,
148 proxy_logging_obj,
149 user_api_key_cache,
150 )
152 if prisma_client is None: 152 ↛ 153line 152 didn't jump to line 153 because the condition on line 152 was never true
153 raise ManagementProblem(
154 ProblemDetail(
155 type=f"{PROBLEM_TYPE_BASE}database-not-connected",
156 title="Database not connected",
157 status=503,
158 detail=CommonProxyErrors.db_not_connected_error.value,
159 )
160 )
162 results: Final = await bulk_delete_users(
163 data=data,
164 user_api_key_dict=user_api_key_dict,
165 prisma_client=prisma_client,
166 user_api_key_cache=user_api_key_cache,
167 proxy_logging_obj=proxy_logging_obj,
168 litellm_proxy_admin_name=litellm_proxy_admin_name,
169 litellm_changed_by=litellm_changed_by,
170 )
171 return BulkDeleteUsersResponse(data=results)
173 except ManagementProblem:
174 raise
175 except Exception as e: # noqa: BLE001 # a driver error answers as a problem document, not the OpenAI error shape
176 verbose_proxy_logger.exception(
177 "litellm.proxy.management_endpoints.management_v1.users.bulk_delete_users_action(): Exception occured - %s",
178 e,
179 )
180 raise ManagementProblem(
181 ProblemDetail(
182 type=f"{PROBLEM_TYPE_BASE}internal-server-error",
183 title="Internal server error",
184 status=500,
185 detail="Failed to delete users.",
186 )
187 )