BerriAI/litellm · warning · HTTPException

Maximum {MAX_BATCH_SIZE} keys can be updated at once. Found

Error message

Maximum {MAX_BATCH_SIZE} keys can be updated at once. Found {len(data.key_ids)} key_ids.

What it means

LiteLLM Proxy's bulk team-key update endpoint (POST /team/key/bulk_update) refuses requests whose key_ids array exceeds MAX_BATCH_SIZE (500). The guard runs before any database work, so nothing is modified when it fires. It exists to keep the per-key update loop (one DB write per key) from monopolizing the DB on a single huge request.

Source

Thrown at litellm/proxy/management_endpoints/key_management_endpoints.py:3146

        user_api_key_cache,
        user_custom_key_update,
    )

    if prisma_client is None:
        raise HTTPException(
            status_code=500,
            detail={"error": "Database not connected"},
        )

    if not data.team_id:
        raise HTTPException(
            status_code=400,
            detail={"error": "team_id is required"},
        )

    MAX_BATCH_SIZE: Final = 500
    if data.key_ids is not None and len(data.key_ids) > MAX_BATCH_SIZE:
        raise HTTPException(
            status_code=400,
            detail={
                "error": f"Maximum {MAX_BATCH_SIZE} keys can be updated at once. Found {len(data.key_ids)} key_ids."
            },
        )

    if data.all_keys_in_team:
        # "all" excludes blocked/expired — bulk refresh shouldn't revive a key an admin disabled.
        # `blocked` is Boolean? with no default; `/key/generate` writes NULL. Prisma's `NOT`
        # excludes NULLs, so explicitly OR `false` with `null` to include them.
        now: Final = datetime.now(timezone.utc)
        existing_keys = await VerificationTokenRepository(prisma_client).table.find_many(
            where={
                "team_id": data.team_id,
                "AND": [
                    {"OR": [{"blocked": False}, {"blocked": None}]},
                    {"OR": [{"expires": None}, {"expires": {"gt": now}}]},
                ],

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Split key_ids into chunks of at most 500 and issue one POST /team/key/bulk_update request per chunk.
  2. If you intended to update every active key in the team, set all_keys_in_team=true instead of listing key_ids — but note that path only works when the team has <=500 keys (it returns a separate error otherwise).
  3. Track which chunk failed from the per-key successful_updates/failed_updates arrays in BulkUpdateKeyResponse and retry only those keys.

Example fix

# before
resp = client.post("/team/key/bulk_update", json={"team_id": tid, "key_ids": all_key_ids, "update_fields": {...}})

# after
CHUNK = 500
for i in range(0, len(all_key_ids), CHUNK):
    resp = client.post("/team/key/bulk_update", json={
        "team_id": tid,
        "key_ids": all_key_ids[i:i+CHUNK],
        "update_fields": {...},
    })
    resp.raise_for_status()
Defensive patterns

Strategy: validation

Validate before calling

MAX_BATCH = 500

def validate_bulk_update_payload(team_id: str, key_ids: list[str]) -> None:
    if len(key_ids) > MAX_BATCH:
        raise ValueError(f"chunk key_ids to <= {MAX_BATCH}; got {len(key_ids)}")

validate_bulk_update_payload(team_id, all_key_ids)

Type guard

def is_valid_batch(key_ids: list[str] | None) -> bool:
    return key_ids is not None and 0 < len(key_ids) <= 500

Prevention

When it happens

Trigger: POST /team/key/bulk_update with a JSON body containing more than 500 entries in key_ids (e.g. 501+ key hashes/aliases), or a script that fetches the full team key list and blindly sends it back in one request.

Common situations: Automation that syncs metadata for every key on a large shared team account; a UI 'select all + update' action over a big team; migrating thousands of keys during onboarding and trying to do it in a single call.

Related errors


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/5079dfe3150ffe5c. Report an issue: GitHub.