refactor(ui): remove the deprecated API Reference page (#51246)

The API Reference page was already behind a deprecation banner. This deletes
the /ui/api-reference route and its components, the Developer Tools sidebar
entry and page description, the legacy ?page=api_ref redirects, and the i18n
strings only that page used.

Old ?page=api_ref and ?page=api-reference bookmarks now fall through to the
dashboard like any other unknown page id, since there is no replacement page.

Tests that listed the page are updated: the sidebar and legacy-route vitest
files, the uiHref examples, the e2e sidebar fixtures and Internal Viewer nav
check, and the UI path detection unit test.
This commit is contained in:
yuneng-jiang 2026-11-11 14:55:32 -08:00 • committed by GitHub
parent 534a60d28d
commit 63d2f4fef7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 9 additions and 337 deletions

View file

@ -14,7 +14,6 @@ export const menuLabelToPage: Record<string, Page> = {
"Internal Users": Page.Users,
"Internal User": Page.Users, // Legacy label support
Organizations: Page.Organizations,
"API Reference": Page.ApiRef,
"AI Hub": Page.ModelHubTable,
"Model Hub": Page.ModelHubTable,
Logs: Page.Logs,

View file

@ -13,11 +13,6 @@ export const MIGRATED_E2E_PAGES: Readonly<Record<string, MigratedPage>> = {
linkName: "Models + Endpoints",
content: { role: "heading", name: "Model Management" },
},
api_ref: {
segment: "api-reference",
linkName: "API Reference",
content: { role: "heading", name: "OpenAI Compatible Proxy: API Reference" },
},
"llm-playground": { segment: "playground", linkName: "Playground", content: { role: "tab", name: "Chat" } },
projects: {
segment: "projects",

View file

@ -10,7 +10,6 @@ export enum Page {
Teams = "teams",
Organizations = "organizations",
AdminPanel = "admin-panel",
ApiRef = "api_ref",
LoggingAndAlerts = "logging-and-alerts",
Budgets = "budgets",
Guardrails = "guardrails",

View file

@ -32,7 +32,6 @@ test.describe("Internal Viewer", () => {
"Usage",
"Logs",
"Teams",
"API Reference",
"AI Hub",
];
for (const label of expectedVisible) {

View file

@ -121,7 +121,7 @@ class TestUIDetectionLogic:
# Verify routes exist
found_routes = 0
expected_routes = ["login", "guardrails", "logs", "api-reference"]
expected_routes = ["login", "guardrails", "logs"]
for route in expected_routes:
route_index = os.path.join(self.temp_dir, route, "index.html")
if os.path.exists(route_index):

View file

@ -1,92 +0,0 @@
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import APIReferenceView from "./APIReferenceView";
vi.mock("@/components/CodeBlock", () => ({
__esModule: true,
default: ({ code }: { code: string }) => <pre data-testid="api-reference-code-block">{code}</pre>,
}));
describe("APIReferenceView", () => {
const codeBlockTestId = "api-reference-code-block";
it("uses the API doc base url when provided", () => {
const apiDocUrl = "https://docs.litellm.test";
render(<APIReferenceView proxySettings={{ LITELLM_UI_API_DOC_BASE_URL: apiDocUrl }} />);
const codeBlocks = screen.getAllByTestId(codeBlockTestId);
expect(codeBlocks[0]).toHaveTextContent(new RegExp(apiDocUrl));
});
it("falls back to the proxy base url when the docs url is missing", () => {
const proxyUrl = "https://proxy.litellm.test";
render(<APIReferenceView proxySettings={{ PROXY_BASE_URL: proxyUrl }} />);
const codeBlocks = screen.getAllByTestId(codeBlockTestId);
expect(codeBlocks[0]).toHaveTextContent(new RegExp(proxyUrl));
});
it("prefers the docs url when both urls are provided", () => {
const apiDocUrl = "https://docs-preferred.litellm.test";
const proxyUrl = "https://proxy-backup.litellm.test";
render(
<APIReferenceView
proxySettings={{
LITELLM_UI_API_DOC_BASE_URL: apiDocUrl,
PROXY_BASE_URL: proxyUrl,
}}
/>,
);
const codeBlocks = screen.getAllByTestId(codeBlockTestId);
const renderedCode = codeBlocks[0].textContent ?? "";
expect(renderedCode).toContain(apiDocUrl);
expect(renderedCode).not.toContain(proxyUrl);
});
it("renders the page title, blurb and docs link", () => {
render(<APIReferenceView proxySettings={{ PROXY_BASE_URL: "https://proxy.litellm.test" }} />);
expect(screen.getByText("OpenAI Compatible Proxy: API Reference")).toBeInTheDocument();
expect(screen.getByText(/LiteLLM is OpenAI Compatible/)).toBeInTheDocument();
const docsLink = screen.getByRole("link", { name: /API Reference Docs/ });
expect(docsLink).toHaveAttribute("href", "https://docs.litellm.ai/docs/proxy/user_keys");
expect(docsLink).toHaveAttribute("target", "_blank");
});
it("exposes the three SDK tabs with the first selected by default", () => {
render(<APIReferenceView proxySettings={{ PROXY_BASE_URL: "https://proxy.litellm.test" }} />);
expect(screen.getAllByRole("tab").map((tab) => tab.textContent)).toEqual([
"OpenAI Python SDK",
"LlamaIndex",
"Langchain Py",
]);
expect(screen.getAllByRole("tab").map((tab) => tab.getAttribute("aria-selected"))).toEqual([
"true",
"false",
"false",
]);
});
it.each([
["OpenAI Python SDK", "import openai"],
["LlamaIndex", "from llama_index.llms import AzureOpenAI"],
["Langchain Py", "from langchain.chat_models import ChatOpenAI"],
])("selecting %s shows its snippet wired to the base url", async (tabName, marker) => {
const proxyUrl = "https://proxy.litellm.test";
const user = userEvent.setup();
render(<APIReferenceView proxySettings={{ PROXY_BASE_URL: proxyUrl }} />);
await user.click(screen.getByRole("tab", { name: tabName }));
expect(screen.getByRole("tab", { name: tabName })).toHaveAttribute("aria-selected", "true");
const selectedPanel = screen.getByRole("tabpanel");
expect(selectedPanel).toHaveTextContent(new RegExp(marker));
expect(selectedPanel).toHaveTextContent(new RegExp(proxyUrl));
});
});

View file

@ -1,142 +0,0 @@
"use client";
import React from "react";
import { useTranslation } from "react-i18next";
import CodeBlock from "@/components/CodeBlock";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import DocLink from "./DocLink";
import { productNames } from "@/i18n/untranslated/productNames";
interface ApiRefProps {
proxySettings: {
PROXY_BASE_URL?: string;
LITELLM_UI_API_DOC_BASE_URL?: string | null;
};
}
const APIReferenceView: React.FC<ApiRefProps> = ({ proxySettings }) => {
const { t } = useTranslation();
let base_url = "<your_proxy_base_url>";
const customDocBaseUrl = proxySettings?.LITELLM_UI_API_DOC_BASE_URL;
if (customDocBaseUrl && customDocBaseUrl.trim()) {
base_url = customDocBaseUrl;
} else if (proxySettings?.PROXY_BASE_URL) {
base_url = proxySettings.PROXY_BASE_URL;
}
return (
<div className="grid grid-cols-1 gap-2 p-8 h-[80vh] w-full mt-2">
<div className="mb-5">
{/* Header row with Docs link on the right */}
<div className="flex items-center justify-between">
<h1 className="text-2xl font-semibold text-foreground">{t("apiReference.view.title")}</h1>
<DocLink className="ml-3 shrink-0" href="https://docs.litellm.ai/docs/proxy/user_keys" />
</div>
<p className="mt-2 mb-2 text-sm text-muted-foreground">{t("apiReference.view.description")} </p>
<Tabs defaultValue="openai">
<TabsList variant="line" className="border-b rounded-none w-full justify-start h-auto p-0">
<TabsTrigger value="openai" className="rounded-none px-4 py-2 flex-none">
{productNames.openAIPythonSdk}
</TabsTrigger>
<TabsTrigger value="llamaindex" className="rounded-none px-4 py-2 flex-none">
{productNames.llamaIndex}
</TabsTrigger>
<TabsTrigger value="langchain" className="rounded-none px-4 py-2 flex-none">
{productNames.langchainPython}
</TabsTrigger>
</TabsList>
<TabsContent value="openai" keepMounted>
<CodeBlock
language="python"
code={`import openai
client = openai.OpenAI(
api_key="your_api_key",
base_url="${base_url}" # LiteLLM Proxy is OpenAI compatible, Read More: https://docs.litellm.ai/docs/proxy/user_keys
)
response = client.chat.completions.create(
model="gpt-3.5-turbo", # model to send to the proxy
messages = [
{
"role": "user",
"content": "this is a test request, write a short poem"
}
]
)
print(response)`}
/>
</TabsContent>
<TabsContent value="llamaindex" keepMounted>
<CodeBlock
language="python"
code={`import os, dotenv
from llama_index.llms import AzureOpenAI
from llama_index.embeddings import AzureOpenAIEmbedding
from llama_index import VectorStoreIndex, SimpleDirectoryReader, ServiceContext
llm = AzureOpenAI(
engine="azure-gpt-3.5", # model_name on litellm proxy
temperature=0.0,
azure_endpoint="${base_url}", # litellm proxy endpoint
api_key="<your-master-key>", # litellm proxy API Key
api_version="2023-07-01-preview",
)
embed_model = AzureOpenAIEmbedding(
deployment_name="azure-embedding-model",
azure_endpoint="${base_url}",
api_key="<your-master-key>",
api_version="2023-07-01-preview",
)
documents = SimpleDirectoryReader("llama_index_data").load_data()
service_context = ServiceContext.from_defaults(llm=llm, embed_model=embed_model)
index = VectorStoreIndex.from_documents(documents, service_context=service_context)
query_engine = index.as_query_engine()
response = query_engine.query("What did the author do growing up?")
print(response)`}
/>
</TabsContent>
<TabsContent value="langchain" keepMounted>
<CodeBlock
language="python"
code={`from langchain.chat_models import ChatOpenAI
from langchain.prompts.chat import (
ChatPromptTemplate,
HumanMessagePromptTemplate,
SystemMessagePromptTemplate,
)
from langchain.schema import HumanMessage, SystemMessage
chat = ChatOpenAI(
openai_api_base="${base_url}",
model = "gpt-3.5-turbo",
temperature=0.1
)
messages = [
SystemMessage(
content="You are a helpful assistant that im using to make a test request to."
),
HumanMessage(
content="test from litellm. tell me why it's amazing in 1 sentence"
),
]
response = chat(messages)
print(response)`}
/>
</TabsContent>
</Tabs>
</div>
</div>
);
};
export default APIReferenceView;

View file

@ -1,33 +0,0 @@
import React from "react";
import { ExternalLink } from "lucide-react";
import { useTranslation } from "react-i18next";
import { cn } from "@/lib/cva.config";
export type DocLinkProps = {
href?: string;
className?: string;
};
const DocLink = ({ href, className }: DocLinkProps) => {
const { t } = useTranslation();
return (
<a
href={href}
target="_blank"
rel="noopener noreferrer"
title={t("apiReference.docLink.title")}
className={cn(
"inline-flex items-center gap-2 rounded-xl border border-border bg-card/80 px-3.5 py-2 text-sm font-medium text-foreground shadow-xs",
"hover:bg-card focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring active:translate-y-[0.5px]",
className,
)}
>
<span>{t("apiReference.docLink.label")}</span>
<ExternalLink aria-hidden className="h-4 w-4 opacity-80" />
<span className="sr-only">{t("apiReference.docLink.newTab")}</span>
</a>
);
};
export default DocLink;

View file

@ -1,22 +0,0 @@
"use client";
import { useTranslation } from "react-i18next";
import APIReferenceView from "./_components/APIReferenceView";
import { DeprecationBanner } from "@/components/DeprecationBanner";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import useProxySettings from "@/app/(dashboard)/hooks/proxySettings/useProxySettings";
const APIReferencePage = () => {
const { t } = useTranslation();
const { accessToken } = useAuthorized();
const proxySettings = useProxySettings(accessToken);
return (
<>
<DeprecationBanner featureName={t("apiReference.page.featureName")} />
<APIReferenceView proxySettings={proxySettings} />
</>
);
};
export default APIReferencePage;

View file

@ -14,8 +14,6 @@ describe("legacyPageRedirectHref", () => {
});
it("keeps the older aliases for renamed pages", () => {
expect(redirect("page=api_ref")).toBe("/ui/api-reference");
expect(redirect("page=api-reference")).toBe("/ui/api-reference");
expect(redirect("page=claude-code-plugins")).toBe("/ui/skills");
});

View file

@ -5,8 +5,6 @@ const LEGACY_PAGE_ROUTES: ReadonlyMap<string, string> = new Map(
home: "home",
"api-keys": "api-keys",
models: "models-and-endpoints",
api_ref: "api-reference",
"api-reference": "api-reference",
"llm-playground": "playground",
projects: "projects",
chat: "chat",

View file

@ -282,7 +282,6 @@ describe("Sidebar (leftnav)", () => {
"Organizations",
"Access Groups",
"Budgets",
"API Reference",
"AI Hub",
"Experimental",
"Settings",
@ -701,7 +700,6 @@ describe("Sidebar (leftnav)", () => {
expectHref("Models + Endpoints", "/ui/models-and-endpoints");
expectHref("Usage", "/ui/usage");
expectHref("Alerts", "/ui/alerts");
expectHref("API Reference", "/ui/api-reference");
});
it("never links a leaf to the legacy ?page= switch", () => {

View file

@ -28,7 +28,6 @@ import {
Building2,
Boxes,
ChevronRight,
Code2,
Database,
ExternalLink,
FileText,
@ -420,13 +419,6 @@ const menuGroups: MenuGroup[] = [
{
groupLabel: "DEVELOPER TOOLS",
items: [
{
key: "api_ref",
page: "api_ref",
route: "api-reference",
label: "shell.nav.items.apiReference",
icon: <Code2 {...ICON} />,
},
{
key: "model-hub-table",
page: "model-hub-table",

View file

@ -388,7 +388,7 @@ describe("UI config and public endpoints", () => {
await Networking.getUiConfig();
expect(Networking.serverRootPath).toBe("/litellm");
expect(uiHref("api-reference")).toBe("/litellm/ui/api-reference");
expect(uiHref("logs")).toBe("/litellm/ui/logs");
});
it("sends model group connection probes to the configured gateway", async () => {

View file

@ -34,7 +34,6 @@ export const pageDescriptions: Record<string, ParseKeys> = {
projects: "shell.pageDescriptions.projects",
"access-groups": "shell.pageDescriptions.accessGroups",
budgets: "shell.pageDescriptions.budgets",
api_ref: "shell.pageDescriptions.apiRef",
"model-hub-table": "shell.pageDescriptions.modelHubTable",
caching: "shell.pageDescriptions.caching",
"transform-request": "shell.pageDescriptions.transformRequest",

View file

@ -8035,7 +8035,6 @@
"organizations": "Organizations",
"accessGroups": "Access Groups",
"budgets": "Budgets",
"apiReference": "API Reference",
"aiHub": "AI Hub",
"responseCache": "Response Cache",
"experimental": "Experimental",
@ -8082,7 +8081,6 @@
"projects": "Manage projects within teams",
"accessGroups": "Manage access groups for role-based permissions",
"budgets": "Set and monitor spending budgets",
"apiRef": "Browse API documentation and endpoints",
"modelHubTable": "Explore available AI models and providers",
"caching": "Configure response caching and coordination Redis settings",
"transformRequest": "Set up request transformation rules",
@ -8554,20 +8552,6 @@
"empty": "No posts yet"
}
},
"apiReference": {
"page": {
"featureName": "The API Reference tab"
},
"view": {
"title": "OpenAI Compatible Proxy: API Reference",
"description": "LiteLLM is OpenAI Compatible. This means your API Key works with the OpenAI SDK. Just replace the base_url to point to your litellm proxy. Example Below"
},
"docLink": {
"title": "Open documentation in a new tab",
"label": "API Reference Docs",
"newTab": "(opens in a new tab)"
}
},
"uiTheme": {
"settings": {
"toasts": {

View file

@ -9,16 +9,16 @@ afterEach(() => {
describe("uiHref", () => {
it("builds a /ui-rooted path when serverRootPath is /", () => {
expect(uiHref("api-reference")).toBe("/ui/api-reference");
expect(uiHref("logs")).toBe("/ui/logs");
});
it("prefixes a non-root serverRootPath without duplicating slashes", () => {
setServerRootPath("/team-x/");
expect(uiHref("api-reference")).toBe("/team-x/ui/api-reference");
expect(uiHref("logs")).toBe("/team-x/ui/logs");
});
it("tolerates a leading slash in the route segment", () => {
expect(uiHref("/api-reference")).toBe("/ui/api-reference");
expect(uiHref("/logs")).toBe("/ui/logs");
});
it("stays root-relative under next dev, which serves the app at /", () => {
@ -29,8 +29,8 @@ describe("uiHref", () => {
describe("routeSegmentForPathname", () => {
it("strips the /ui base and any trailing slash", () => {
expect(routeSegmentForPathname("/ui/api-reference")).toBe("api-reference");
expect(routeSegmentForPathname("/ui/api-reference/")).toBe("api-reference");
expect(routeSegmentForPathname("/ui/logs")).toBe("logs");
expect(routeSegmentForPathname("/ui/logs/")).toBe("logs");
});
it("returns an empty segment for the dashboard root", () => {

View file

@ -10,12 +10,12 @@ function uiBase(): string {
return `${root}/ui`;
}
/** Absolute (same-origin) href for a dashboard route segment, e.g. "api-reference" -> "/ui/api-reference". */
/** Absolute (same-origin) href for a dashboard route segment, e.g. "logs" -> "/ui/logs". */
export function uiHref(routeSegment: string): string {
return `${uiBase()}/${routeSegment.replace(/^\/+/, "")}`;
}
/** First route segment under the UI base, e.g. "/ui/api-reference/" -> "api-reference" and "/ui/" -> "". */
/** First route segment under the UI base, e.g. "/ui/logs/" -> "logs" and "/ui/" -> "". */
export function routeSegmentForPathname(pathname: string): string {
const base = uiBase();
const relative = pathname.startsWith(base) ? pathname.slice(base.length) : pathname;