{"record":{"id":"28997a3f2ba3a75a","repo":"BerriAI/litellm","slug":"team-data-team-id-has-more-than-max-batch-size","errorCode":null,"errorMessage":"Team {data.team_id} has more than {MAX_BATCH_SIZE} keys. Use `key_ids` to update in batches of {MAX_BATCH_SIZE}.","messagePattern":"Team (.+?) has more than (.+?) keys\\. Use `key_ids` to update in batches of (.+?)\\.","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"litellm/proxy/management_endpoints/key_management_endpoints.py","lineNumber":3170,"sourceCode":"\n    if data.all_keys_in_team:\n        # \"all\" excludes blocked/expired — bulk refresh shouldn't revive a key an admin disabled.\n        # `blocked` is Boolean? with no default; `/key/generate` writes NULL. Prisma's `NOT`\n        # excludes NULLs, so explicitly OR `false` with `null` to include them.\n        now: Final = datetime.now(timezone.utc)\n        existing_keys = await VerificationTokenRepository(prisma_client).table.find_many(\n            where={\n                \"team_id\": data.team_id,\n                \"AND\": [\n                    {\"OR\": [{\"blocked\": False}, {\"blocked\": None}]},\n                    {\"OR\": [{\"expires\": None}, {\"expires\": {\"gt\": now}}]},\n                ],\n            },\n            order={\"token\": \"asc\"},\n            take=MAX_BATCH_SIZE + 1,\n        )\n        if len(existing_keys) > MAX_BATCH_SIZE:\n            raise HTTPException(\n                status_code=400,\n                detail={\n                    \"error\": f\"Team {data.team_id} has more than {MAX_BATCH_SIZE} keys. Use `key_ids` to update in batches of {MAX_BATCH_SIZE}.\"\n                },\n            )\n        requested_tokens = [row.token for row in existing_keys]\n    else:\n        if data.key_ids is None or len(data.key_ids) == 0:\n            raise HTTPException(\n                status_code=400,\n                detail={\"error\": \"key_ids must be provided when all_keys_in_team is False\"},\n            )\n        # Dedupe by hashed form — duplicates collapse to one update.\n        requested_tokens = []\n        hashed_key_ids: Final = []\n        seen_hashes: Final = set()\n        for k in data.key_ids:\n            h = _hash_token_if_needed(k)","sourceCodeStart":3152,"sourceCodeEnd":3188,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/management_endpoints/key_management_endpoints.py#L3152-L3188","documentation":"On POST /team/key/bulk_update with all_keys_in_team=true, LiteLLM fetches the team's active keys (not blocked, not expired) with take=MAX_BATCH_SIZE+1. If it gets back more than 500 rows it aborts with this 400 rather than silently updating only the first 500. It is a completeness guard: the 'all keys' mode is only allowed when it can actually mean all keys.","triggerScenarios":"POST /team/key/bulk_update with {\"team_id\": ..., \"all_keys_in_team\": true} on a team that has 501 or more active (unblocked, unexpired) keys.","commonSituations":"Bulk-rotating budgets or metadata on a large organization-wide team; teams grown past 500 keys through automated key issuance; a 'rotate all team keys' admin script.","solutions":["Switch to the key_ids mode: query the team's key tokens (e.g. GET /team/info with expand keys, or /key/info batch) and then POST them in chunks of <=500 with all_keys_in_team omitted/false.","If most keys are stale, clean up blocked/expired keys first — they are excluded from the 500 count, so pruning may drop the team under the limit.","Split very large teams into multiple smaller teams if bulk operations are a regular workflow."],"exampleFix":"# before\nclient.post(\"/team/key/bulk_update\", json={\"team_id\": tid, \"all_keys_in_team\": True, \"update_fields\": {...}})\n\n# after\nkeys = get_all_team_key_ids(tid)  # via /team/info or /v2/key/info\nfor i in range(0, len(keys), 500):\n    client.post(\"/team/key/bulk_update\", json={\n        \"team_id\": tid,\n        \"key_ids\": keys[i:i+500],\n        \"update_fields\": {...},\n    })","handlingStrategy":"validation","validationCode":"# Before using all_keys_in_team, check the team's active key count\ninfo = client.get(\"/team/info\", params={\"team_id\": tid}).json()\nactive_keys = count_active_team_keys(info)  # exclude blocked/expired\nif active_keys > 500:\n    key_ids = fetch_team_key_ids(tid)  # then chunk these","typeGuard":"def can_use_all_keys_mode(active_key_count: int) -> bool:\n    return active_key_count <= 500","tryCatchPattern":"try:\n    resp = client.post(\"/team/key/bulk_update\", json={\"team_id\": tid, \"all_keys_in_team\": True, ...})\n    resp.raise_for_status()\nexcept HTTPError as e:\n    if e.response.status_code == 400 and \"more than\" in e.response.text:\n        switch_to_chunked_key_ids_mode(tid)","preventionTips":["Monitor team key counts and alert before a team crosses 500 active keys.","Prefer explicit key_ids lists in automation — 'all keys' mode is inherently fragile at scale.","Prune blocked/expired keys regularly; they no longer count toward the 500 limit."],"tags":["batch-limit","key-management","bulk-update","pagination","litellm-proxy"],"backgroundTag":"batch-size-limit-exceeded","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}