BerriAI/litellm · error · HTTPException

Failed to fetch analytics: {e}

Error message

Failed to fetch analytics: {e}

What it means

This is the catch-all of get_daily_activity: every step after the guards (building WHERE conditions, the count query, the paginated spend query, metadata resolution, metrics aggregation) runs inside try/except; any exception is logged by verbose_proxy_logger as 'Error fetching daily activity: <e>' and re-raised as HTTP 500 'Failed to fetch analytics: <e}'. The interpolated text is the real cause — typical ones are unparsable date strings reaching dateutil, SQL/Prisma errors, or unexpected None fields in spend rows during aggregation.

Source

Thrown at litellm/proxy/management_endpoints/common_daily_activity.py:1094

                total_tokens=metadata_metrics.total_tokens,
                total_api_requests=metadata_metrics.api_requests,
                total_successful_requests=metadata_metrics.successful_requests,
                total_failed_requests=metadata_metrics.failed_requests,
                total_cache_read_input_tokens=metadata_metrics.cache_read_input_tokens,
                total_cache_creation_input_tokens=metadata_metrics.cache_creation_input_tokens,
                total_compression_saved_tokens=metadata_metrics.compression_saved_tokens,
                total_compression_savings_spend=metadata_metrics.compression_savings_spend,
                total_prompt_caching_savings_spend=metadata_metrics.prompt_caching_savings_spend,
                total_autorouter_savings_spend=metadata_metrics.autorouter_savings_spend,
                page=page,
                total_pages=-(-total_count // page_size),  # Ceiling division
                has_more=(page * page_size) < total_count,
            ),
        )

    except Exception as e:
        verbose_proxy_logger.exception("Error fetching daily activity: %s", e)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail={"error": f"Failed to fetch analytics: {e}"},
        )


async def get_daily_activity_aggregated(
    prisma_client: PrismaClient | None,
    table_name: str,
    entity_id_field: str,
    entity_id: str | list[str] | None,
    entity_metadata_field: Mapping[str, dict[str, object]] | None,
    start_date: str | None,
    end_date: str | None,
    model: str | None,
    api_key: str | None,
    exclude_entity_ids: list[str] | None = None,
    timezone_offset_minutes: int | None = None,
) -> SpendAnalyticsPaginatedResponse:

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Read the 'Failed to fetch analytics: <e>' detail — it names the actual exception
  2. If date-related: normalize dates to ISO 8601 (YYYY-MM-DD) client-side and retry
  3. If DB-related: check Postgres health, proxy logs for the full stack trace, and that migrations ran for the spend tables
  4. Narrow the date window or add filters (model, api_key) to reduce query size if timeouts are the cause

Example fix

# before
curl "...?start_date=Aug 1, 2026&end_date=18/08/2026"  # 500 Failed to fetch analytics

# after
curl "...?start_date=2026-08-01&end_date=2026-08-18"  # 200
Defensive patterns

Strategy: retry

Validate before calling

from datetime import datetime

def valid_analytics_params(params: dict) -> bool:
    for k in ("start_date", "end_date"):
        v = params.get(k)
        if not isinstance(v, str) or not v:
            return False
        try:
            datetime.fromisoformat(v)
        except ValueError:
            return False
    return True

assert valid_analytics_params(params), "dates must be present and ISO-formatted"

Try / catch

import httpx, time

def fetch_daily_activity(url, hdrs, params, attempts=3):
    last = None
    for i in range(attempts):
        try:
            r = httpx.get(url, params=params, headers=hdrs, timeout=60)
            if r.status_code == 200:
                return r.json()
            if r.status_code == 500 and "Failed to fetch analytics" in r.text:
                last = r.text  # root cause is interpolated + logged server-side
                time.sleep(2 ** i)
                continue
            r.raise_for_status()
        except httpx.TransportError as e:
            last = str(e)
            time.sleep(2 ** i)
    raise RuntimeError(f"daily activity fetch failed: {last}")

Prevention

When it happens

Trigger: Passing a start_date/end_date value that is present but not date-parsable (e.g. '2026-13-45' or 'yesterday-ish'); DB errors mid-query (connection drop, permission on spend tables); malformed api_key/model filter values that break SQL parameter binding.

Common situations: Datepickers emitting locale-specific formats; long-running analytics queries hitting connection pool timeouts; schemas drifted after restoring a backup so spend tables lack expected columns.

Related errors


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