BerriAI/litellm · warning · ValueError

limit must be an integer

Error message

limit must be an integer

What it means

ValueError from LiteLLMDatabase.get_usage_data when the limit parameter cannot be converted with int(limit) — either a TypeError (type has no int conversion, e.g. a list or None-like object passed despite the None check) or ValueError (a non-numeric string like 'all' or '1.5.2'). This is input validation before the SQL LIMIT clause is appended.

Source

Thrown at litellm/integrations/cloudzero/database.py:92

        FROM "LiteLLM_DailyUserSpend" dus
        LEFT JOIN "LiteLLM_VerificationToken" vt ON dus.api_key = vt.token
        LEFT JOIN "LiteLLM_TeamTable" tt ON vt.team_id = tt.team_id
        LEFT JOIN "LiteLLM_UserTable" ut ON dus.user_id = ut.user_id
        WHERE ($1::timestamptz IS NULL OR dus.updated_at >= $1::timestamptz)
          AND ($2::timestamptz IS NULL OR dus.updated_at <= $2::timestamptz)
        ORDER BY dus.date DESC, dus.created_at DESC
        """

        params: Final[list[Any]] = [
            start_time_utc,
            end_time_utc,
        ]

        if limit is not None:
            try:
                params.append(int(limit))
            except (TypeError, ValueError):
                raise ValueError("limit must be an integer")
            query += " LIMIT $3"

        try:
            db_response: Final = await client.db.query_raw(query, *params)
            # Convert the response to polars DataFrame with full schema inference
            # This prevents schema mismatch errors when data types vary across rows
            return pl.DataFrame(db_response, infer_schema_length=None)
        except Exception as e:
            raise Exception(f"Error retrieving usage data: {e}")

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Pass limit as an actual int, or omit it entirely for no limit
  2. Coerce at the boundary: int(str_limit) inside your own try/except before calling
  3. Reject non-numeric strings early with a clear 400-style error in your caller
  4. For 'unlimited' semantics, pass limit=None, not a sentinel string

Example fix

# before
df = await db.get_usage_data(limit=request.args.get("limit"))  # 'all' → ValueError

# after
raw = request.args.get("limit")
limit = int(raw) if raw and raw.isdigit() else None
df = await db.get_usage_data(limit=limit)
Defensive patterns

Strategy: validation

Validate before calling

def coerce_limit(raw) -> int | None:
    if raw is None:
        return None
    try:
        return int(raw)
    except (TypeError, ValueError):
        return None  # or raise your own 400 error

Type guard

def is_int_like_limit(raw) -> bool:
    if raw is None:
        return True
    if isinstance(raw, bool):
        return False
    try:
        int(raw)
        return True
    except (TypeError, ValueError):
        return False

Try / catch

try:
    df = await db.get_usage_data(limit=raw_limit)
except ValueError as e:
    if "limit must be an integer" in str(e):
        df = await db.get_usage_data(limit=None)
    else:
        raise

Prevention

When it happens

Trigger: Calling get_usage_data(limit='all'), limit='1.5.2', or limit=[100] — typically when the value arrives from a CLI flag, HTTP query param, or config file without prior type coercion. Floats like '1.5' fail int() too even though they look numeric.

Common situations: Script or scheduler config passing limit as a string from YAML/JSON; users typing 'unlimited'/'all'; API query params not cast to int at the boundary.

Related errors


AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15). Data as JSON: /api/errors/8115e5927975e262. Report an issue: GitHub.