BerriAI/litellm · error · HTTPException
Not all keys passed in were deleted. This probably means you
Error message
Not all keys passed in were deleted. This probably means you don't have access to delete all the keys passed in. Keys passed in={num_keys_to_be_deleted}, Deleted keys ={number_deleted_keys} What it means
After deleting, /key/delete compares the number of keys you asked to delete with the number actually deleted. A mismatch means some keys were skipped by the ownership filter — the handler enforces that a non-admin caller can only delete keys they own (delete_verification_tokens scopes the where-clause by user). It surfaces as a 400 with both counts, telling you the request was only partially applied.
Source
Thrown at litellm/proxy/management_endpoints/key_management_endpoints.py:3434
)
num_keys_to_be_deleted = len(data.key_aliases)
deleted_keys = data.key_aliases
else:
raise ValueError("Invalid request type")
if number_deleted_keys is None:
raise ProxyException(
message="Failed to delete keys got None response from delete_verification_token",
type=ProxyErrorTypes.internal_server_error,
param="keys",
code=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
verbose_proxy_logger.debug("/key/delete - deleted_keys=%s", number_deleted_keys)
try:
assert num_keys_to_be_deleted == len(deleted_keys)
except Exception:
raise HTTPException(
status_code=400,
detail={
"error": f"Not all keys passed in were deleted. This probably means you don't have access to delete all the keys passed in. Keys passed in={num_keys_to_be_deleted}, Deleted keys ={number_deleted_keys}"
},
)
verbose_proxy_logger.debug(
"/keys/delete - cache after delete: %s", user_api_key_cache.in_memory_cache.cache_dict
)
asyncio.create_task(
KeyManagementEventHooks.async_key_deleted_hook(
data=data,
keys_being_deleted=_keys_being_deleted,
user_api_key_dict=user_api_key_dict,
litellm_changed_by=litellm_changed_by,
response=number_deleted_keys,
)View on GitHub (pinned to 77b7c6c40c)
Solutions
- Re-authenticate with the proxy admin master key if you intend to delete keys you don't own.
- Filter your list to keys that still exist and that the caller owns before sending (verify via GET /key/info).
- Treat this as partial success: reconcile which keys remain and send a second request only for those you're allowed to delete.
Example fix
# before (user key deleting others' keys)
client.headers["Authorization"] = "Bearer sk-user-key"
client.post("/key/delete", json={"keys": all_team_keys})
# after (admin master key, existing keys only)
client.headers["Authorization"] = "Bearer sk-master"
existing = [k for k in all_team_keys if key_exists(k)]
client.post("/key/delete", json={"keys": existing}) Defensive patterns
Strategy: validation
Validate before calling
# Pre-filter to keys the caller can delete (exist + owned) when not using the master key
infos = client.post("/v2/key/info", json={"keys": candidate_keys}).json()
deletable = [k["token"] for k in infos if k and k.get("user_id") == caller_user_id]
client.post("/key/delete", json={"keys": deletable}) Try / catch
try:
client.post("/key/delete", json={"keys": keys})
except HTTPError as e:
if e.response.status_code == 400 and "Not all keys passed in were deleted" in e.response.text:
remaining = reconcile_remaining_keys(keys) # diff against /v2/key/info
if remaining:
escalate("delete requires admin key for foreign keys: %s", remaining)
else:
raise Prevention
- Use the proxy admin master key for bulk/cleanup deletes spanning multiple owners.
- Delete only keys verified to still exist; already-deleted keys deflate the deleted count and trip the check.
- Treat a 400 here as partial success and reconcile state instead of blind-retrying the same list.
When it happens
Trigger: Calling POST /key/delete with keys belonging to other users/teams while authenticated with a non-admin key; including already-deleted keys (delete_many counts only rows removed this call); mixing valid and invalid tokens in one request.
Common situations: A cleanup script with a stale key list where some keys were already deleted; a team member trying to purge all team keys without proxy-admin credentials; deleting a key owned by a service account from a personal key.
Related errors
- User={key.user_id} is not a member of the team={team.team_id
- User={change_initiated_by.user_id} is not a Proxy Admin or T
- You are not allowed to access this key's info. Your role={us
- Unable to record skill ownership: caller has no identity sco
- Skill not found: {skill_id}
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/0112eb13a6c18035.
Report an issue: GitHub.