diff --git a/litellm/proxy/db/autorouter_session_rollup.py b/litellm/proxy/db/autorouter_session_rollup.py
index 9c637a62dc1..b33bffbaaa1 100644
--- a/litellm/proxy/db/autorouter_session_rollup.py
+++ b/litellm/proxy/db/autorouter_session_rollup.py
@@ -37,7 +37,9 @@ CACHE_TTL_1H_SECONDS: Final = 3600
AUTOROUTER_BENCHMARKS_SQL: Final = """
WITH windowed AS (
SELECT * FROM "LiteLLM_AutoRouterSession"
- WHERE last_turn_at >= $1::timestamp AND first_turn_at < $2::timestamp
+ WHERE last_turn_at >= $1::timestamp
+ AND first_turn_at < $2::timestamp
+ AND ($3::text IS NULL OR api_key = $3::text)
),
tier_maps AS (
SELECT router_name, router_type, jsonb_object_agg(tier, tier_turns) AS tier_turns
diff --git a/litellm/proxy/management_endpoints/auto_router_endpoints.py b/litellm/proxy/management_endpoints/auto_router_endpoints.py
index 2a7813bc140..0f0323b45f8 100644
--- a/litellm/proxy/management_endpoints/auto_router_endpoints.py
+++ b/litellm/proxy/management_endpoints/auto_router_endpoints.py
@@ -645,6 +645,7 @@ async def get_auto_router_benchmarks(
str | None, Query(description="YYYY-MM-DD UTC, inclusive (defaults to 30 days before end_date)")
] = None,
end_date: Annotated[str | None, Query(description="YYYY-MM-DD UTC, inclusive (defaults to today)")] = None,
+ api_key: Annotated[str | None, Query(description="Filter to one virtual key token hash")] = None,
) -> AutoRouterBenchmarksResponse:
"""
Benchmarks for the auto-router dashboard: session shape, savings against the configured
@@ -681,6 +682,7 @@ async def get_auto_router_benchmarks(
AUTOROUTER_BENCHMARKS_SQL,
start_day.isoformat(),
(end_day + timedelta(days=1)).isoformat(),
+ api_key,
)
rows: Final = _SESSION_AGG_ROWS.validate_python(raw_rows or ())
groups: Final = (
diff --git a/tests/proxy_behavior/spend/test_autorouter_session_rollup.py b/tests/proxy_behavior/spend/test_autorouter_session_rollup.py
index 7bc61c40ea0..c2272f3d20d 100644
--- a/tests/proxy_behavior/spend/test_autorouter_session_rollup.py
+++ b/tests/proxy_behavior/spend/test_autorouter_session_rollup.py
@@ -169,6 +169,7 @@ async def test_the_benchmarks_aggregate_reads_only_overlapping_sessions(db):
AUTOROUTER_BENCHMARKS_SQL,
(T0 - timedelta(days=1)).isoformat(),
(T0 + timedelta(days=1)).isoformat(),
+ None,
)
matching = [row for row in rows if row["router_name"] == router]
assert len(matching) == 1
@@ -181,6 +182,33 @@ async def test_the_benchmarks_aggregate_reads_only_overlapping_sessions(db):
assert grouped["session_seconds"] == pytest.approx(60.0)
+async def test_the_benchmarks_aggregate_can_filter_to_one_key(db):
+ router = f"r-{uuid.uuid4()}"
+ first_key = f"k-{uuid.uuid4()}"
+ second_key = f"k-{uuid.uuid4()}"
+ await _turn(db, first_key, "A", T0, session_id=f"s-{uuid.uuid4()}", router=router, saved=0.5)
+ await _turn(db, second_key, "A", T0, session_id=f"s-{uuid.uuid4()}", router=router, saved=9.0)
+
+ rows = await db.query_raw(
+ AUTOROUTER_BENCHMARKS_SQL,
+ (T0 - timedelta(days=1)).isoformat(),
+ (T0 + timedelta(days=1)).isoformat(),
+ first_key,
+ )
+ matching = [row for row in rows if row["router_name"] == router]
+ assert len(matching) == 1
+ assert matching[0]["sessions"] == 1
+ assert matching[0]["saved_spend"] == pytest.approx(0.5)
+
+ unknown_key_rows = await db.query_raw(
+ AUTOROUTER_BENCHMARKS_SQL,
+ (T0 - timedelta(days=1)).isoformat(),
+ (T0 + timedelta(days=1)).isoformat(),
+ f"k-{uuid.uuid4()}",
+ )
+ assert [row for row in unknown_key_rows if row["router_name"] == router] == []
+
+
async def test_a_reconfigured_alias_reports_each_router_type_as_its_own_group(db):
key = f"k-{uuid.uuid4()}"
router = f"r-{uuid.uuid4()}"
@@ -191,6 +219,7 @@ async def test_a_reconfigured_alias_reports_each_router_type_as_its_own_group(db
AUTOROUTER_BENCHMARKS_SQL,
(T0 - timedelta(days=1)).isoformat(),
(T0 + timedelta(days=1)).isoformat(),
+ None,
)
matching = sorted(
(row for row in rows if row["router_name"] == router),
@@ -253,6 +282,7 @@ async def test_the_benchmarks_aggregate_sums_tier_turns_across_sessions(db):
AUTOROUTER_BENCHMARKS_SQL,
(T0 - timedelta(days=1)).isoformat(),
(T0 + timedelta(days=1)).isoformat(),
+ None,
)
grouped = next(row for row in rows if row["router_name"] == router)
assert grouped["tier_turns"] == {"simple": 2, "complex": 1}
@@ -280,6 +310,7 @@ async def test_tier_maps_stay_separate_per_router_type_on_a_reconfigured_alias(d
AUTOROUTER_BENCHMARKS_SQL,
(T0 - timedelta(days=1)).isoformat(),
(T0 + timedelta(days=1)).isoformat(),
+ None,
)
by_type = {row["router_type"]: row["tier_turns"] for row in rows if row["router_name"] == router}
assert by_type == {"complexity": {"medium": 1}, "quality": {"2": 1}}
@@ -294,6 +325,7 @@ async def test_a_window_with_no_tiered_turns_aggregates_to_an_empty_map(db):
AUTOROUTER_BENCHMARKS_SQL,
(T0 - timedelta(days=1)).isoformat(),
(T0 + timedelta(days=1)).isoformat(),
+ None,
)
grouped = next(row for row in rows if row["router_name"] == router)
assert grouped["tier_turns"] == {}
diff --git a/tests/test_litellm/proxy/management_endpoints/test_auto_router_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_auto_router_endpoints.py
index 21f8d985f22..35e76c96c14 100644
--- a/tests/test_litellm/proxy/management_endpoints/test_auto_router_endpoints.py
+++ b/tests/test_litellm/proxy/management_endpoints/test_auto_router_endpoints.py
@@ -489,6 +489,7 @@ class TestAutoRouterBenchmarks:
monkeypatch: pytest.MonkeyPatch,
rows: Sequence[Mapping[str, object]],
model_list: Sequence[object],
+ api_key: str | None = None,
) -> AutoRouterBenchmarksResponse:
from litellm.proxy import proxy_server
from litellm.proxy.management_endpoints.auto_router_endpoints import get_auto_router_benchmarks
@@ -503,6 +504,7 @@ class TestAutoRouterBenchmarks:
user_api_key_dict=ADMIN,
start_date="2026-07-01",
end_date="2026-08-01",
+ api_key=api_key,
)
ROW = _SessionAggRow(
@@ -652,8 +654,9 @@ class TestAutoRouterBenchmarks:
user_api_key_dict=ADMIN,
start_date="2026-07-01",
end_date="2026-08-01",
+ api_key="key-hash",
)
- assert captured["params"] == ("2026-07-01T00:00:00", "2026-08-02T00:00:00")
+ assert captured["params"] == ("2026-07-01T00:00:00", "2026-08-02T00:00:00", "key-hash")
assert response.routers_in_scope == 1
assert response.groups[0].router_name == "live-auto"
assert response.groups[0].saved_pct == response.totals.saved_pct == 75.0
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/AutoRouterBenchmarksTab.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/AutoRouterBenchmarksTab.test.tsx
index ce0cd75cd36..e7c6adc478c 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/AutoRouterBenchmarksTab.test.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/AutoRouterBenchmarksTab.test.tsx
@@ -22,7 +22,7 @@ vi.mock("@/components/shared/advanced_date_picker", () => ({
import { useAutoRouters } from "@/app/(dashboard)/hooks/models/useModels";
-import AutoRouterBenchmarksTab from "./AutoRouterBenchmarksTab";
+import AutoRouterBenchmarksTab, { AutoRouterUsageView } from "./AutoRouterBenchmarksTab";
import type {
AutoRouterBenchmarkGroup,
AutoRouterBenchmarksResponse,
@@ -359,13 +359,37 @@ describe("AutoRouterBenchmarksTab", () => {
mockHook({ data: response([group()]) });
const { dateValue, onDateChange } = renderTab();
- expect(vi.mocked(useAutoRouterBenchmarks)).toHaveBeenCalledWith("sk-test", dateValue);
+ expect(vi.mocked(useAutoRouterBenchmarks)).toHaveBeenCalledWith("sk-test", dateValue, undefined);
expect(screen.getByText("Jul 6 – Aug 5 (UTC)")).toBeInTheDocument();
fireEvent.click(screen.getByTestId("date-picker"));
expect(onDateChange).toHaveBeenCalledWith({ from: new Date(2026, 7, 1), to: new Date(2026, 7, 5) });
});
+ it("scopes the query to one key when the usage view is mounted for a key", () => {
+ mockHook({ data: response([group()]) });
+ const dateValue = { from: new Date(2026, 6, 6), to: new Date(2026, 7, 5) };
+ const activity = {
+ dateValue,
+ onDateChange: vi.fn(),
+ results: [],
+ loading: false,
+ isFetchingMore: false,
+ progress: { currentPage: 1, totalPages: 1 },
+ cancelled: false,
+ cancel: vi.fn(),
+ };
+ render(
+
+
+ ,
+ );
+
+ expect(vi.mocked(useAutoRouterBenchmarks)).toHaveBeenCalledWith("sk-test", dateValue, "key-hash-1");
+ expect(screen.getByText("Total estimated savings")).toBeInTheDocument();
+ expect(screen.queryByRole("tab", { name: "Shadow Evals" })).not.toBeInTheDocument();
+ });
+
it("shows usage by default and mounts shadow evals only when its sub-tab is selected", () => {
mockHook({ data: response([group()]) });
renderTab();
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/AutoRouterBenchmarksTab.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/AutoRouterBenchmarksTab.tsx
index 09a0cf0242b..39e6b0fd390 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/AutoRouterBenchmarksTab.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/AutoRouterBenchmarksTab.tsx
@@ -273,12 +273,13 @@ const BenchmarksBody: React.FC = ({ isPending, error, data,
interface AutoRouterBenchmarksTabProps {
accessToken: string | null;
- activity: DailyActivityRange;
+ activity: Pick;
+ apiKey?: string;
}
-const UsageView: React.FC = ({ accessToken, activity }) => {
+export const AutoRouterUsageView: React.FC = ({ accessToken, activity, apiKey }) => {
const { dateValue, onDateChange } = activity;
- const { data, isPending, error } = useAutoRouterBenchmarks(accessToken, dateValue);
+ const { data, isPending, error } = useAutoRouterBenchmarks(accessToken, dateValue, apiKey);
const [selectedKey, setSelectedKey] = useState(ALL_ROUTERS);
const { data: autoRouters } = useAutoRouters();
@@ -347,7 +348,7 @@ const AutoRouterBenchmarksTab: React.FC = ({ acces
-
+
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/useAutoRouterBenchmarks.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/useAutoRouterBenchmarks.test.ts
index e8a858ef3a5..1258c967ec9 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/useAutoRouterBenchmarks.test.ts
+++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/useAutoRouterBenchmarks.test.ts
@@ -3,7 +3,9 @@ import { describe, expect, it, vi } from "vitest";
vi.mock("@/components/networking", () => ({ formatDate: vi.fn() }));
vi.mock("@/lib/http/api", () => ({ $api: { useQuery: vi.fn() } }));
-import { benchmarksWindow } from "./useAutoRouterBenchmarks";
+import { $api } from "@/lib/http/api";
+
+import { benchmarksWindow, useAutoRouterBenchmarks } from "./useAutoRouterBenchmarks";
const localDay =
(offsetHours: number) =>
@@ -44,3 +46,30 @@ describe("benchmarksWindow", () => {
expect(benchmarksWindow({ to: now }, now, pacific)).toEqual({});
});
});
+
+describe("useAutoRouterBenchmarks", () => {
+ const range = { from: new Date("2026-07-06T19:00:00Z"), to: new Date("2026-08-05T19:00:00Z") };
+
+ it("forwards the key hash as the endpoint's api_key filter", () => {
+ useAutoRouterBenchmarks("sk-test", range, "key-hash-1");
+
+ const [, path, init] = vi.mocked($api.useQuery).mock.calls.at(-1) as unknown as [
+ string,
+ string,
+ { params: { query: Record } },
+ ];
+ expect(path).toBe("/auto_router/benchmarks");
+ expect(init.params.query.api_key).toBe("key-hash-1");
+ });
+
+ it("leaves the read deployment-wide when no key is given", () => {
+ useAutoRouterBenchmarks("sk-test", range);
+
+ const [, , init] = vi.mocked($api.useQuery).mock.calls.at(-1) as unknown as [
+ string,
+ string,
+ { params: { query: Record } },
+ ];
+ expect(init.params.query.api_key).toBeUndefined();
+ });
+});
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/useAutoRouterBenchmarks.ts b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/useAutoRouterBenchmarks.ts
index 155c9d0c696..284342f0aec 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/useAutoRouterBenchmarks.ts
+++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/useAutoRouterBenchmarks.ts
@@ -23,10 +23,10 @@ export const benchmarksWindow = (
};
};
-export const useAutoRouterBenchmarks = (accessToken: string | null, range: DateRange) =>
+export const useAutoRouterBenchmarks = (accessToken: string | null, range: DateRange, apiKey?: string) =>
$api.useQuery(
"get",
"/auto_router/benchmarks",
- { params: { query: benchmarksWindow(range, new Date()) } },
+ { params: { query: { ...benchmarksWindow(range, new Date()), api_key: apiKey } } },
{ enabled: Boolean(accessToken && range.from && range.to), retry: false },
);
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/useDailyActivityRange.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/useDailyActivityRange.test.tsx
index 6c9281060a4..00902aa9fdd 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/useDailyActivityRange.test.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/useDailyActivityRange.test.tsx
@@ -25,11 +25,19 @@ vi.mock("@/components/networking", () => ({
}));
import { userDailyActivityAggregatedCall } from "@/components/networking";
-import { useDailyActivityRange } from "./useDailyActivityRange";
+import { useActivityDateRange, useDailyActivityRange } from "./useDailyActivityRange";
const argsOfLastCall = () => mockUsePaginatedDailyActivity.mock.calls.at(-1)?.[0].args as unknown[];
describe("useDailyActivityRange", () => {
+ it("offers date-range state without starting a daily-activity query", () => {
+ const { result } = renderHook(() => useActivityDateRange());
+
+ expect(result.current.dateValue.from).toBeInstanceOf(Date);
+ expect(result.current.dateValue.to).toBeInstanceOf(Date);
+ expect(mockUsePaginatedDailyActivity).not.toHaveBeenCalled();
+ });
+
it("queries every user's activity for an admin", () => {
renderHook(() => useDailyActivityRange("test-token", "u1", "proxy_admin"));
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/useDailyActivityRange.ts b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/useDailyActivityRange.ts
index 8ed57e36a94..234144f4423 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/useDailyActivityRange.ts
+++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/useDailyActivityRange.ts
@@ -37,13 +37,18 @@ export interface DailyActivityScope {
apiKey?: string | null;
}
+export const useActivityDateRange = (): Pick => {
+ const initialFrom = useMemo(() => new Date(new Date().getTime() - THIRTY_DAYS_MS), []);
+ const initialTo = useMemo(() => new Date(), []);
+ const [dateValue, setDateValue] = useState({ from: initialFrom, to: initialTo });
+ return { dateValue, onDateChange: setDateValue };
+};
+
export const useScopedDailyActivityRange = (
accessToken: string | null,
scope: DailyActivityScope,
): DailyActivityRange => {
- const initialFrom = useMemo(() => new Date(new Date().getTime() - THIRTY_DAYS_MS), []);
- const initialTo = useMemo(() => new Date(), []);
- const [dateValue, setDateValue] = useState({ from: initialFrom, to: initialTo });
+ const { dateValue, onDateChange } = useActivityDateRange();
const startTime = dateValue.from ?? null;
const endTime = dateValue.to ?? null;
@@ -63,7 +68,7 @@ export const useScopedDailyActivityRange = (
return {
dateValue,
- onDateChange: setDateValue,
+ onDateChange,
results: data.results as DailyData[],
loading,
isFetchingMore,
diff --git a/ui/litellm-dashboard/src/components/templates/KeyAutoRouterUsageTab.integration.test.tsx b/ui/litellm-dashboard/src/components/templates/KeyAutoRouterUsageTab.integration.test.tsx
new file mode 100644
index 00000000000..69d2f1b896b
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/templates/KeyAutoRouterUsageTab.integration.test.tsx
@@ -0,0 +1,92 @@
+import { screen } from "@testing-library/react";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+import { renderWithProviders, testQueryClient } from "../../../tests/test-utils";
+import KeyAutoRouterUsageTab from "./KeyAutoRouterUsageTab";
+
+vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
+ default: () => ({ accessToken: "test-token", userId: "admin-123", userRole: "Admin" }),
+}));
+
+const jsonResponse = (body: unknown) =>
+ new Response(JSON.stringify(body), { status: 200, headers: { "content-type": "application/json" } });
+
+const cache = {
+ coverage_pct: 100,
+ hit_rate_pct: 50,
+ same_model: { turns: 2, hits: 1, hit_rate_pct: 50 },
+ first_visit: { turns: 1, hits: 0, hit_rate_pct: 0 },
+ return_to_tier: { turns: 1, hits: 1, hit_rate_pct: 100 },
+ unordered_turns: 0,
+ return_misses_expired: 0,
+ return_misses_within_ttl: 0,
+ return_misses_unknown: 0,
+ ttl_5m_turns: 0,
+ ttl_1h_turns: 0,
+};
+
+const stats = {
+ sessions: 2,
+ turns: 4,
+ avg_turns_per_session: 2,
+ avg_session_seconds: 30,
+ avg_tokens_per_session: 100,
+ spend: 1.25,
+ saved_spend: 8.75,
+ baseline_spend: 10,
+ saved_pct: 87.5,
+ saved_per_session: 4.375,
+ cache,
+};
+
+const benchmarks = {
+ start_date: "2025-01-01",
+ end_date: "2025-01-31",
+ routers_in_scope: 2,
+ totals: stats,
+ groups: [
+ { router_name: "router-one", router_type: "complexity", tier_turns: { SIMPLE: 4 }, ...stats },
+ {
+ router_name: "router-two",
+ router_type: "complexity",
+ tier_turns: { SIMPLE: 1 },
+ ...stats,
+ spend: 0.25,
+ saved_spend: 0.75,
+ baseline_spend: 1,
+ },
+ ],
+};
+
+const noDeployments = { data: [], total_count: 0, current_page: 1, total_pages: 1, size: 1000 };
+const fetchMock = vi.fn(async (request: Request | string) => {
+ const url = typeof request === "string" ? request : request.url;
+ if (url.includes("/auto_router/benchmarks")) return jsonResponse(benchmarks);
+ return jsonResponse(noDeployments);
+});
+const requestedUrls = () =>
+ fetchMock.mock.calls.map(([request]) => (typeof request === "string" ? request : request.url));
+
+describe("KeyAutoRouterUsageTab", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ testQueryClient.clear();
+ vi.stubGlobal("fetch", fetchMock);
+ });
+
+ it("renders this key's spend, baseline, savings and per-router filter", async () => {
+ renderWithProviders();
+
+ expect(await screen.findByText("$8.75")).toBeInTheDocument();
+ expect(screen.getByText("Actual auto-router spend")).toBeInTheDocument();
+ expect(screen.getByText("$1.25")).toBeInTheDocument();
+ expect(screen.getByText("Estimated spend at highest-tier model")).toBeInTheDocument();
+ expect(screen.getByText("$10.00")).toBeInTheDocument();
+ expect(screen.getByText("Auto-router prompt caching")).toBeInTheDocument();
+ expect(screen.getAllByText("50.0%").length).toBeGreaterThan(0);
+ expect(screen.getByText("All auto-routers")).toBeInTheDocument();
+
+ const benchmarkUrl = new URL(requestedUrls().find((url) => url.includes("/auto_router/benchmarks")) ?? "");
+ expect(benchmarkUrl.searchParams.get("api_key")).toBe("key-hash-1");
+ });
+});
diff --git a/ui/litellm-dashboard/src/components/templates/KeyAutoRouterUsageTab.tsx b/ui/litellm-dashboard/src/components/templates/KeyAutoRouterUsageTab.tsx
new file mode 100644
index 00000000000..a2a7b9079a5
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/templates/KeyAutoRouterUsageTab.tsx
@@ -0,0 +1,17 @@
+"use client";
+
+import React from "react";
+
+import { AutoRouterUsageView } from "@/app/(dashboard)/cost-optimization/_components/AutoRouterBenchmarksTab";
+import { useActivityDateRange } from "@/app/(dashboard)/cost-optimization/_components/useDailyActivityRange";
+
+interface KeyAutoRouterUsageTabProps {
+ accessToken: string | null;
+ keyToken: string;
+}
+
+const KeyAutoRouterUsageTab: React.FC = ({ accessToken, keyToken }) => (
+
+);
+
+export default KeyAutoRouterUsageTab;
diff --git a/ui/litellm-dashboard/src/components/templates/key_info_view.test.tsx b/ui/litellm-dashboard/src/components/templates/key_info_view.test.tsx
index 7817d4e7cea..feee1cee9e6 100644
--- a/ui/litellm-dashboard/src/components/templates/key_info_view.test.tsx
+++ b/ui/litellm-dashboard/src/components/templates/key_info_view.test.tsx
@@ -26,6 +26,10 @@ vi.mock("./key_edit_view", () => ({
},
}));
+vi.mock("./KeyAutoRouterUsageTab", () => ({
+ default: ({ keyToken }: { keyToken: string }) => {keyToken}
,
+}));
+
vi.mock("@/app/(dashboard)/hooks/useTeams", () => ({
default: vi.fn(),
}));
@@ -176,6 +180,22 @@ describe("KeyInfoView", () => {
await userEvent.click(await screen.findByRole("button", { name: /more key actions/i }));
};
+ it("shows key-scoped auto-router usage as its own admin tab", async () => {
+ vi.mocked(useAuthorized).mockReturnValue({ ...baseUseAuthorizedMock, userRole: "Admin" });
+ renderWithProviders( {}} keyId="test-key-id" teams={[]} />);
+
+ await userEvent.click(screen.getByRole("tab", { name: "Auto-router usage" }));
+
+ expect(screen.getByTestId("key-auto-router-usage")).toHaveTextContent("test-token-123");
+ });
+
+ it("does not offer the admin-only auto-router usage tab to an internal user", () => {
+ vi.mocked(useAuthorized).mockReturnValue({ ...baseUseAuthorizedMock, userRole: "Internal User" });
+ renderWithProviders( {}} keyId="test-key-id" teams={[]} />);
+
+ expect(screen.queryByRole("tab", { name: "Auto-router usage" })).not.toBeInTheDocument();
+ });
+
describe("last updated", () => {
const renderWithTimestamps = (overrides: Partial) => {
vi.mocked(useAuthorized).mockReturnValue(baseUseAuthorizedMock);
diff --git a/ui/litellm-dashboard/src/components/templates/key_info_view.tsx b/ui/litellm-dashboard/src/components/templates/key_info_view.tsx
index 0dd0dd6d6af..86bbfd73410 100644
--- a/ui/litellm-dashboard/src/components/templates/key_info_view.tsx
+++ b/ui/litellm-dashboard/src/components/templates/key_info_view.tsx
@@ -16,8 +16,14 @@ import { modelGroupHref, teamDetailHref } from "@/utils/entityLinks";
import { BadgeLink } from "@/components/shared/BadgeLink";
import { KeyInfoHeader } from "./KeyInfoHeader";
import KeySavingsTab from "./KeySavingsTab";
+import KeyAutoRouterUsageTab from "./KeyAutoRouterUsageTab";
import { useEffect, useState } from "react";
-import { isProxyAdminRole, isUserTeamAdminForSingleTeam, rolesWithWriteAccess } from "../../utils/roles";
+import {
+ hasProxyWideSpendView,
+ isProxyAdminRole,
+ isUserTeamAdminForSingleTeam,
+ rolesWithWriteAccess,
+} from "../../utils/roles";
import { mapDisplayToInternalNames, mapInternalToDisplayNames } from "../callback_info_helpers";
import AutoRotationView from "../common_components/AutoRotationView";
import DeleteResourceModal from "../common_components/DeleteResourceModal";
@@ -618,6 +624,11 @@ export default function KeyInfoView({
Savings
+ {hasProxyWideSpendView(userRole) && (
+
+ Auto-router usage
+
+ )}
Settings
@@ -764,6 +775,12 @@ export default function KeyInfoView({
/>
+ {hasProxyWideSpendView(userRole) && (
+
+
+
+ )}
+
{/* Settings Panel */}
diff --git a/ui/litellm-dashboard/src/lib/http/schema.d.ts b/ui/litellm-dashboard/src/lib/http/schema.d.ts
index b1534c19670..799f0e7ef32 100644
--- a/ui/litellm-dashboard/src/lib/http/schema.d.ts
+++ b/ui/litellm-dashboard/src/lib/http/schema.d.ts
@@ -41392,6 +41392,8 @@ export interface operations {
start_date?: string | null;
/** @description YYYY-MM-DD UTC, inclusive (defaults to today) */
end_date?: string | null;
+ /** @description Filter to one virtual key token hash */
+ api_key?: string | null;
};
header?: never;
path?: never;