From 210560e1e76f5d2f1ad4c94dcb3e41303c710ae2 Mon Sep 17 00:00:00 2001 From: AlexsanderHamir Date: Wed, 26 Nov 2025 15:48:43 -0800 Subject: [PATCH 1/3] Add paginated /spend/logs/v2 endpoint - Add /spend/logs/v2 endpoint that shares implementation with /spend/logs/ui - Provides paginated access to spend logs with comprehensive filtering - Replaces non-paginated /spend/logs endpoint to prevent performance issues - Both v2 and ui endpoints share the same function for consistency --- .../spend_management_endpoints.py | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/litellm/proxy/spend_tracking/spend_management_endpoints.py b/litellm/proxy/spend_tracking/spend_management_endpoints.py index 5dfedcc0b8..2c9dc3fc09 100644 --- a/litellm/proxy/spend_tracking/spend_management_endpoints.py +++ b/litellm/proxy/spend_tracking/spend_management_endpoints.py @@ -1,5 +1,6 @@ #### SPEND MANAGEMENT ##### import collections +import json import os from datetime import datetime, timedelta, timezone from functools import lru_cache @@ -1609,6 +1610,14 @@ async def calculate_spend(request: SpendCalculateRequest): ) +@router.get( + "/spend/logs/v2", + tags=["Budget & Spend Tracking"], + dependencies=[Depends(user_api_key_auth)], + responses={ + 200: {"model": Dict[str, Any]}, + }, +) @router.get( "/spend/logs/ui", tags=["Budget & Spend Tracking"], @@ -1672,16 +1681,16 @@ async def ui_view_spend_logs( # noqa: PLR0915 ), ): """ - View spend logs for UI with pagination support + View spend logs with pagination support. + Available at both `/spend/logs/v2` (public API) and `/spend/logs/ui` (internal UI). - Returns: - { - "data": List[LiteLLM_SpendLogs], # Paginated spend logs - "total": int, # Total number of records - "page": int, # Current page number - "page_size": int, # Number of items per page - "total_pages": int # Total number of pages - } + Returns paginated response with data, total, page, page_size, and total_pages. + + Example: + ``` + curl -X GET "http://0.0.0.0:8000/spend/logs/v2?start_date=2025-11-25%2000:00:00&end_date=2025-11-26%2023:59:59&page=1&page_size=50" \ +-H "Authorization: Bearer sk-1234" + ``` """ from litellm.proxy.proxy_server import prisma_client From 1ad9e014abfea81174bb2bcf4112af1f6d3e658f Mon Sep 17 00:00:00 2001 From: AlexsanderHamir Date: Wed, 26 Nov 2025 16:11:53 -0800 Subject: [PATCH 2/3] Add endpoint-based date parsing for /spend/logs/v2 - Add flexible date parsing for v2 endpoint (supports both YYYY-MM-DD and YYYY-MM-DD HH:MM:SS) - Keep strict timestamp format for /spend/logs/ui endpoint for backward compatibility - Parse dates based on which endpoint was called using request path --- .../spend_management_endpoints.py | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/litellm/proxy/spend_tracking/spend_management_endpoints.py b/litellm/proxy/spend_tracking/spend_management_endpoints.py index 2c9dc3fc09..608db6af9a 100644 --- a/litellm/proxy/spend_tracking/spend_management_endpoints.py +++ b/litellm/proxy/spend_tracking/spend_management_endpoints.py @@ -7,7 +7,7 @@ from functools import lru_cache from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional import fastapi -from fastapi import APIRouter, Depends, HTTPException, status +from fastapi import APIRouter, Depends, HTTPException, Request, status import litellm from litellm._logging import verbose_proxy_logger @@ -1628,6 +1628,7 @@ async def calculate_spend(request: SpendCalculateRequest): }, ) async def ui_view_spend_logs( # noqa: PLR0915 + request: Request, api_key: Optional[str] = fastapi.Query( default=None, description="Get spend logs based on api key", @@ -1711,13 +1712,24 @@ async def ui_view_spend_logs( # noqa: PLR0915 ) try: - # Convert the date strings to datetime objects - start_date_obj = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S").replace( - tzinfo=timezone.utc - ) - end_date_obj = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S").replace( - tzinfo=timezone.utc - ) + is_v2 = "/spend/logs/v2" in request.url.path + formats = ["%Y-%m-%d %H:%M:%S", "%Y-%m-%d"] if is_v2 else ["%Y-%m-%d %H:%M:%S"] + + def parse_date(date_str: str) -> datetime: + date_str = date_str.strip() + for fmt in formats: + try: + return datetime.strptime(date_str, fmt).replace(tzinfo=timezone.utc) + except ValueError: + continue + expected = "'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'" if is_v2 else "'YYYY-MM-DD HH:MM:SS'" + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"Invalid date format: {date_str}. Expected: {expected}", + ) + + start_date_obj = parse_date(start_date) + end_date_obj = parse_date(end_date) # Convert to ISO format strings for Prisma start_date_iso = start_date_obj.isoformat() # Already in UTC, no need to add Z From df190d25b87ced96cce2d2940e0f2b770256856c Mon Sep 17 00:00:00 2001 From: AlexsanderHamir Date: Wed, 26 Nov 2025 16:16:13 -0800 Subject: [PATCH 3/3] Add deprecation notice to /spend/logs endpoint - Mark /spend/logs as deprecated in docstring - Direct users to use /spend/logs/v2 for paginated access - Warns about performance issues with non-paginated endpoint --- litellm/proxy/spend_tracking/spend_management_endpoints.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/litellm/proxy/spend_tracking/spend_management_endpoints.py b/litellm/proxy/spend_tracking/spend_management_endpoints.py index 608db6af9a..2d3fc023a3 100644 --- a/litellm/proxy/spend_tracking/spend_management_endpoints.py +++ b/litellm/proxy/spend_tracking/spend_management_endpoints.py @@ -1917,6 +1917,9 @@ async def view_spend_logs( # noqa: PLR0915 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), ): """ + [DEPRECATED] This endpoint is not paginated and can cause performance issues. + Please use `/spend/logs/v2` instead for paginated access to spend logs. + View all spend logs, if request_id is provided, only logs for that request_id will be returned When start_date and end_date are provided: