BerriAI/litellm · error · ValueError

start_time_utc and end_time_utc must be provided together

Error message

start_time_utc and end_time_utc must be provided together

What it means

FocusLogger.export_usage_data raises ValueError when exactly one of start_time_utc / end_time_utc is passed. Time bounds only make sense as a pair (they become the FocusTimeWindow); the check is a boolean XOR, so passing either bound alone is rejected before any DB/export work happens.

Source

Thrown at litellm/integrations/focus/focus_logger.py:92

            )
        return self._engine

    async def export_usage_data(
        self,
        *,
        limit: int | None = None,
        start_time_utc: datetime | None = None,
        end_time_utc: datetime | None = None,
    ) -> None:
        """Public hook to trigger export immediately.

        When called without time bounds (manual /vantage/export with no
        start/end), exports **all** available data instead of the last
        scheduled window.  The hourly/daily window only applies to
        automatic scheduler runs.
        """
        if bool(start_time_utc) ^ bool(end_time_utc):
            raise ValueError("start_time_utc and end_time_utc must be provided together")

        if start_time_utc and end_time_utc:
            window: Final = FocusTimeWindow(
                start_time=start_time_utc,
                end_time=end_time_utc,
                frequency=self.frequency,
            )
            await self._export_window(window=window, limit=limit)
        else:
            # No time bounds → export all available data
            await self._export_all(limit=limit)

    async def dry_run_export_usage_data(self, limit: int | None = DEFAULT_DRY_RUN_LIMIT) -> dict[str, Any]:
        """Return transformed data without uploading."""
        engine: Final = self._ensure_engine()
        return await engine.dry_run_export_usage_data(limit=limit)

    async def initialize_focus_export_job(self) -> None:

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Pass both bounds together: export_usage_data(start_time_utc=start, end_time_utc=end).
  2. Or pass neither to export all available data.
  3. In HTTP handlers, default missing params so both are None or both are set (validate at the request boundary).

Example fix

# before
await logger.export_usage_data(start_time_utc=start)

# after
if start and end:
    await logger.export_usage_data(start_time_utc=start, end_time_utc=end)
else:
    await logger.export_usage_data()  # export all
Defensive patterns

Strategy: validation

Validate before calling

if (start_time_utc is None) != (end_time_utc is None):
    raise ValueError("Provide both start_time_utc and end_time_utc, or neither")
await logger.export_usage_data(limit=limit, start_time_utc=start_time_utc, end_time_utc=end_time_utc)

Type guard

def is_valid_window(start: datetime | None, end: datetime | None) -> bool:
    return (start is None) == (end is None)

Prevention

When it happens

Trigger: Calling export_usage_data(start_time_utc=ts) or export_usage_data(end_time_utc=ts) with the other bound omitted — e.g. a caller forwarding optional query params from an HTTP endpoint where only one was supplied.

Common situations: REST handler mapping optional start/end query params straight into the call; a client sending only 'since'; datetime parsed as falsy on one side (note: datetime.min-like values are fine, but None defaults trip it).

Related errors


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