diff --git a/ui/litellm-dashboard/src/components/templates/KeyInfoHeader.test.tsx b/ui/litellm-dashboard/src/components/templates/KeyInfoHeader.test.tsx
index f67f70e7df9..b81495e68e8 100644
--- a/ui/litellm-dashboard/src/components/templates/KeyInfoHeader.test.tsx
+++ b/ui/litellm-dashboard/src/components/templates/KeyInfoHeader.test.tsx
@@ -3,13 +3,20 @@ import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import { KeyInfoHeader, KeyInfoData } from "./KeyInfoHeader";
+vi.mock("next/navigation", () => ({ useRouter: () => ({ push: vi.fn() }) }));
+
const MOCK_DATA: KeyInfoData = {
keyName: "My Test Key",
keyId: "sk-1234567890abcdef",
userId: "user-abc-123",
userEmail: "test@example.com",
userAlias: null,
+ teamId: "team-xyz-789",
+ teamAlias: "Platform Team",
+ orgId: "org-abc-001",
+ orgAlias: "Acme Org",
createdBy: "admin@example.com",
+ createdById: "admin-user-456",
createdAt: "Oct 29, 2025 at 1:26 AM",
lastUpdated: "Oct 29, 2025 at 1:47 AM",
lastActive: "Oct 29, 2025 at 2:00 AM",
@@ -37,6 +44,74 @@ describe("KeyInfoHeader", () => {
expect(screen.getByText("Expires")).toBeInTheDocument();
expect(screen.getByText("Last Updated")).toBeInTheDocument();
expect(screen.getByText("Last Active")).toBeInTheDocument();
+ expect(screen.getByText("Team")).toBeInTheDocument();
+ expect(screen.getByText("Organization")).toBeInTheDocument();
+ });
+
+ describe("entity links", () => {
+ it("links the user to the users page", () => {
+ render();
+ expect(screen.getByRole("link", { name: "test@example.com" })).toHaveAttribute(
+ "href",
+ expect.stringContaining("/users?user=user-abc-123"),
+ );
+ });
+
+ it("links the creator to the users page by user id, not by the displayed alias", () => {
+ render();
+ expect(screen.getByRole("link", { name: "admin@example.com" })).toHaveAttribute(
+ "href",
+ expect.stringContaining("/users?user=admin-user-456"),
+ );
+ });
+
+ it("shows the team alias and links it to the team page by id", () => {
+ render();
+ expect(screen.getByRole("link", { name: "Platform Team" })).toHaveAttribute(
+ "href",
+ expect.stringContaining("/teams?team=team-xyz-789"),
+ );
+ });
+
+ it("falls back to the team id when no alias is known", () => {
+ render();
+ expect(screen.getByRole("link", { name: "team-xyz-789" })).toHaveAttribute(
+ "href",
+ expect.stringContaining("/teams?team=team-xyz-789"),
+ );
+ });
+
+ it("shows the organization alias and links it to the organization page by id", () => {
+ render();
+ expect(screen.getByRole("link", { name: "Acme Org" })).toHaveAttribute(
+ "href",
+ expect.stringContaining("/organizations?org=org-abc-001"),
+ );
+ });
+
+ it("renders '-' without a link when the key has no organization", () => {
+ render();
+ expect(screen.queryByRole("link", { name: /org/i })).not.toBeInTheDocument();
+ expect(screen.getByText("Organization").parentElement?.parentElement).toHaveTextContent("-");
+ });
+
+ it("renders '-' without a link when the key has no team", () => {
+ render();
+ expect(screen.queryByRole("link", { name: /team/i })).not.toBeInTheDocument();
+ expect(screen.getByText("Team").parentElement?.parentElement).toHaveTextContent("-");
+ });
+
+ it("does not link the user when the key has no user id", () => {
+ render();
+ expect(screen.getByText("orphan@example.com")).toBeInTheDocument();
+ expect(screen.queryByRole("link", { name: "orphan@example.com" })).not.toBeInTheDocument();
+ });
+
+ it("keeps the Default Proxy Admin creator unlinked", () => {
+ render();
+ expect(screen.getByText("Default Proxy Admin")).toBeInTheDocument();
+ expect(screen.queryByRole("link", { name: /default/i })).not.toBeInTheDocument();
+ });
});
describe("back button", () => {
diff --git a/ui/litellm-dashboard/src/components/templates/KeyInfoHeader.tsx b/ui/litellm-dashboard/src/components/templates/KeyInfoHeader.tsx
index f31a265da87..cffa356d693 100644
--- a/ui/litellm-dashboard/src/components/templates/KeyInfoHeader.tsx
+++ b/ui/litellm-dashboard/src/components/templates/KeyInfoHeader.tsx
@@ -3,6 +3,7 @@ import {
ArrowLeft,
ArrowLeftRight,
Ban,
+ Building2,
Calendar,
CircleCheck,
Clock,
@@ -13,6 +14,7 @@ import {
Timer,
Trash2,
User,
+ Users,
Zap,
} from "lucide-react";
import { Badge } from "@/components/ui/badge";
@@ -27,6 +29,8 @@ import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/h
import { Separator } from "@/components/ui/separator";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import CopyButton from "@/components/shared/CopyButton";
+import { EntityLink } from "@/components/shared/EntityLink";
+import { orgDetailHref, teamDetailHref, userDetailHref } from "@/utils/entityLinks";
import LabeledField from "../common_components/LabeledField";
import DefaultProxyAdminTag from "../common_components/DefaultProxyAdminTag";
@@ -36,7 +40,12 @@ export interface KeyInfoData {
userId: string;
userEmail: string;
userAlias?: string | null;
+ teamId: string;
+ teamAlias?: string | null;
+ orgId: string;
+ orgAlias?: string | null;
createdBy: string;
+ createdById: string;
createdAt: string;
lastUpdated: string;
lastActive: string;
@@ -135,7 +144,11 @@ function UserField({ userAlias, userEmail, userId }: { userAlias?: string | null
{displayValue}}
+ render={
+
+ {userId ? {displayValue} : displayValue}
+
+ }
/>
{popoverContent}
@@ -265,6 +278,7 @@ export function KeyInfoHeader({
label="Created By"
value={data.createdBy}
icon={}
+ href={data.createdById ? userDetailHref(data.createdById) : undefined}
truncate
copyable
defaultUserIdCheck
@@ -277,6 +291,25 @@ export function KeyInfoHeader({
} />
} />
+
+
+
+
+ }
+ href={data.teamId ? teamDetailHref(data.teamId) : undefined}
+ truncate
+ />
+ }
+ href={data.orgId ? orgDetailHref(data.orgId) : undefined}
+ truncate
+ />
+
);
diff --git a/ui/litellm-dashboard/src/components/templates/KeyInfoView.handleKeyUpdate.test.tsx b/ui/litellm-dashboard/src/components/templates/KeyInfoView.handleKeyUpdate.test.tsx
index 42d1884e563..983c91d87a7 100644
--- a/ui/litellm-dashboard/src/components/templates/KeyInfoView.handleKeyUpdate.test.tsx
+++ b/ui/litellm-dashboard/src/components/templates/KeyInfoView.handleKeyUpdate.test.tsx
@@ -17,9 +17,14 @@ vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
default: mockUseAuthorized,
}));
+vi.mock("@/app/(dashboard)/hooks/organizations/useOrganizations", () => ({
+ useOrganizations: () => ({ data: [] }),
+}));
+
// Networking: wire the hoisted fns so we can assert calls later
vi.mock("../networking", () => {
return {
+ serverRootPath: "",
keyUpdateCall: (...args: any[]) => keyUpdateCallMock(...args),
keyDeleteCall: (...args: any[]) => keyDeleteCallMock(...args),
};
diff --git a/ui/litellm-dashboard/src/components/templates/key_info_view.budget_display.test.tsx b/ui/litellm-dashboard/src/components/templates/key_info_view.budget_display.test.tsx
index bab720f7517..1ffee10710f 100644
--- a/ui/litellm-dashboard/src/components/templates/key_info_view.budget_display.test.tsx
+++ b/ui/litellm-dashboard/src/components/templates/key_info_view.budget_display.test.tsx
@@ -11,6 +11,12 @@ import useTeams from "@/app/(dashboard)/hooks/useTeams";
// where the overview "Spend" card formatted `max_budget` with the default 0
// decimals, truncating sub-dollar budgets (e.g. $0.10) to "$0".
+vi.mock("next/navigation", () => ({ useRouter: () => ({ push: vi.fn() }) }));
+
+vi.mock("@/app/(dashboard)/hooks/organizations/useOrganizations", () => ({
+ useOrganizations: () => ({ data: [] }),
+}));
+
vi.mock("./key_edit_view", () => ({
KeyEditView: () => ,
}));
@@ -24,6 +30,7 @@ vi.mock("@/app/(dashboard)/hooks/keys/useResetKeySpend", () => ({
useResetKeySpend: vi.fn(() => ({ mutate: vi.fn(), isPending: false })),
}));
vi.mock("../networking", () => ({
+ serverRootPath: "",
keyDeleteCall: vi.fn().mockResolvedValue({}),
keyUpdateCall: vi.fn().mockResolvedValue({}),
getPolicyInfoWithGuardrails: vi.fn().mockResolvedValue({ resolved_guardrails: [] }),
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 1437689958c..0d41d199b22 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
@@ -13,6 +13,12 @@ const editViewMocks = vi.hoisted(() => ({
onSubmit: undefined as ((v: Record) => Promise) | undefined,
}));
+vi.mock("next/navigation", () => ({ useRouter: () => ({ push: vi.fn() }) }));
+
+vi.mock("@/app/(dashboard)/hooks/organizations/useOrganizations", () => ({
+ useOrganizations: () => ({ data: [] }),
+}));
+
vi.mock("./key_edit_view", () => ({
KeyEditView: ({ onSubmit }: { onSubmit: (v: Record) => Promise }) => {
editViewMocks.onSubmit = onSubmit;
@@ -53,6 +59,7 @@ import { useMCPServers } from "@/app/(dashboard)/hooks/mcpServers/useMCPServers"
import { useMCPToolsets } from "@/app/(dashboard)/hooks/mcpServers/useMCPToolsets";
vi.mock("../networking", () => ({
+ serverRootPath: "",
keyDeleteCall: vi.fn().mockResolvedValue({}),
keyUpdateCall: vi.fn().mockResolvedValue({}),
getPolicyInfoWithGuardrails: vi.fn().mockResolvedValue({
@@ -487,6 +494,89 @@ describe("KeyInfoView", () => {
});
});
+ describe("entity links in the header", () => {
+ const mockTeam: Team = {
+ team_id: "linked-team-id",
+ team_alias: "Linked Team",
+ models: [],
+ max_budget: null,
+ budget_duration: null,
+ tpm_limit: null,
+ rpm_limit: null,
+ organization_id: "org-1",
+ created_at: "2025-01-01T00:00:00Z",
+ keys: [],
+ members_with_roles: [],
+ spend: 0,
+ };
+
+ beforeEach(() => {
+ vi.mocked(useTeams).mockReturnValue({ teams: [mockTeam], setTeams: vi.fn() });
+ vi.mocked(useAuthorized).mockReturnValue(baseUseAuthorizedMock);
+ });
+
+ it("links the key's team by alias, resolved from the teams list, to the team page", async () => {
+ const keyData = { ...MOCK_KEY_DATA, team_id: "linked-team-id" };
+ renderWithProviders(
+ {}} keyId="test-key-id" onKeyDataUpdate={() => {}} teams={[]} />,
+ );
+
+ expect(await screen.findByRole("link", { name: "Linked Team" })).toHaveAttribute(
+ "href",
+ expect.stringContaining("/teams?team=linked-team-id"),
+ );
+ });
+
+ it("links the key's user and creator to their user pages by id", async () => {
+ const keyData = {
+ ...MOCK_KEY_DATA,
+ user_id: "owner-user-id",
+ user_email: "owner@example.com",
+ created_by: "creator-user-id",
+ created_by_user: { user_id: "creator-user-id", user_email: "creator@example.com", user_alias: null },
+ };
+ renderWithProviders(
+ {}} keyId="test-key-id" onKeyDataUpdate={() => {}} teams={[]} />,
+ );
+
+ expect(await screen.findByRole("link", { name: "owner@example.com" })).toHaveAttribute(
+ "href",
+ expect.stringContaining("/users?user=owner-user-id"),
+ );
+ expect(screen.getByRole("link", { name: "creator@example.com" })).toHaveAttribute(
+ "href",
+ expect.stringContaining("/users?user=creator-user-id"),
+ );
+ });
+
+ it("links the key's organization by id, falling back to the team's organization", async () => {
+ const keyData = { ...MOCK_KEY_DATA, team_id: "linked-team-id", organization_id: null };
+ renderWithProviders(
+ {}} keyId="test-key-id" onKeyDataUpdate={() => {}} teams={[]} />,
+ );
+
+ expect(await screen.findByRole("link", { name: "org-1" })).toHaveAttribute(
+ "href",
+ expect.stringContaining("/organizations?org=org-1"),
+ );
+ });
+
+ it("renders no team link when the key has no team", async () => {
+ renderWithProviders(
+ {}}
+ keyId="test-key-id"
+ onKeyDataUpdate={() => {}}
+ teams={[]}
+ />,
+ );
+
+ await screen.findByText("Team");
+ expect(screen.queryByRole("link", { name: /team/i })).not.toBeInTheDocument();
+ });
+ });
+
it("should call onClose when back button is clicked", async () => {
vi.mocked(useAuthorized).mockReturnValue(baseUseAuthorizedMock);
const onCloseMock = vi.fn();
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 a2d926dff8a..3ee11325a65 100644
--- a/ui/litellm-dashboard/src/components/templates/key_info_view.tsx
+++ b/ui/litellm-dashboard/src/components/templates/key_info_view.tsx
@@ -2,6 +2,7 @@ import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import { useProjects } from "@/app/(dashboard)/hooks/projects/useProjects";
import { useUISettings } from "@/app/(dashboard)/hooks/uiSettings/useUISettings";
import useTeams from "@/app/(dashboard)/hooks/useTeams";
+import { useOrganizations } from "@/app/(dashboard)/hooks/organizations/useOrganizations";
import { formatNumberWithCommas } from "@/utils/dataUtils";
import { mapEmptyStringToNull } from "@/utils/keyUpdateUtils";
import { ArrowLeft } from "lucide-react";
@@ -10,6 +11,8 @@ import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { EntityLink } from "@/components/shared/EntityLink";
+import { teamDetailHref } from "@/utils/entityLinks";
import { KeyInfoHeader } from "./KeyInfoHeader";
import { useEffect, useState } from "react";
import { isProxyAdminRole, isUserTeamAdminForSingleTeam, rolesWithWriteAccess } from "../../utils/roles";
@@ -78,6 +81,7 @@ export default function KeyInfoView({
const queryClient = useQueryClient();
const canEditGuardrails = premiumUser || (userRole != null && rolesWithWriteAccess.includes(userRole));
const { teams: teamsData } = useTeams();
+ const { data: organizations } = useOrganizations();
const { data: projects } = useProjects();
const { data: uiSettingsData } = useUISettings();
const { data: allMcpServers } = useMCPServers();
@@ -453,6 +457,8 @@ export default function KeyInfoView({
const lastConfiguredAt = currentKeyData.settings_updated_at || currentKeyData.created_at;
const parentTeam = currentKeyData.team_id ? teamsData?.find((team) => team.team_id === currentKeyData.team_id) : null;
+ const orgId = currentKeyData.organization_id || currentKeyData.org_id || parentTeam?.organization_id || "";
+ const parentOrg = orgId ? organizations?.find((org) => org.organization_id === orgId) : null;
const budgetDisplay =
currentKeyData.max_budget !== null
@@ -470,11 +476,16 @@ export default function KeyInfoView({
userId: currentKeyData.user_id || "",
userEmail: currentKeyData.user_email || "",
userAlias: currentKeyData.user?.user_alias ?? null,
+ teamId: currentKeyData.team_id || "",
+ teamAlias: parentTeam?.team_alias ?? null,
+ orgId,
+ orgAlias: parentOrg?.organization_alias ?? null,
createdBy:
currentKeyData.created_by_user?.user_alias ||
currentKeyData.created_by_user?.user_email ||
currentKeyData.created_by ||
"",
+ createdById: currentKeyData.created_by_user?.user_id || currentKeyData.created_by || "",
createdAt: currentKeyData.created_at ? formatTimestamp(currentKeyData.created_at) : "",
lastUpdated: lastConfiguredAt ? formatTimestamp(lastConfiguredAt) : "",
lastActive: currentKeyData.last_active ? formatTimestamp(currentKeyData.last_active) : "Never",
@@ -766,7 +777,15 @@ export default function KeyInfoView({
Team ID
-
{currentKeyData.team_id || "Not Set"}
+
+ {currentKeyData.team_id ? (
+
+ {currentKeyData.team_id}
+
+ ) : (
+ "Not Set"
+ )}
+
{enableProjectsUI && (
diff --git a/ui/litellm-dashboard/src/utils/entityLinks.ts b/ui/litellm-dashboard/src/utils/entityLinks.ts
index b0829d15d1d..675ac8d0554 100644
--- a/ui/litellm-dashboard/src/utils/entityLinks.ts
+++ b/ui/litellm-dashboard/src/utils/entityLinks.ts
@@ -11,3 +11,7 @@ export function keyDetailHref(keyToken: string): string {
export function userDetailHref(userId: string): string {
return `${migratedHref("users")}?user=${encodeURIComponent(userId)}`;
}
+
+export function orgDetailHref(orgId: string): string {
+ return `${migratedHref("organizations")}?org=${encodeURIComponent(orgId)}`;
+}