diff --git a/src/App.test.tsx b/src/App.test.tsx
index 4a6affe3..4f1c8079 100644
--- a/src/App.test.tsx
+++ b/src/App.test.tsx
@@ -8,13 +8,17 @@ describe("App", () => {
   it("should render header", async () => {
     render(<App />);
     expect(screen.getByText(/toggle sidebar/i)).toBeVisible();
-    expect(screen.getByText("Certificates")).toBeVisible();
+    expect(screen.getByText("Settings")).toBeVisible();
     expect(screen.getByText("Help")).toBeVisible();
     expect(screen.getByRole("banner", { name: "App header" })).toBeVisible();
     expect(screen.getByRole("heading", { name: /codeGate/i })).toBeVisible();
 
-    await userEvent.click(screen.getByText("Certificates"));
-
+    await userEvent.click(screen.getByText("Settings"));
+    expect(
+      screen.getByRole("menuitem", {
+        name: /providers/i,
+      }),
+    ).toBeVisible();
     expect(
       screen.getByRole("menuitem", {
         name: /certificate security/i,
@@ -26,7 +30,7 @@ describe("App", () => {
       }),
     ).toBeVisible();
 
-    await userEvent.click(screen.getByText("Certificates"));
+    await userEvent.click(screen.getByText("Settings"));
     await userEvent.click(screen.getByText("Help"));
 
     expect(
diff --git a/src/Page.tsx b/src/Page.tsx
index 1abcfcde..65d5c1ce 100644
--- a/src/Page.tsx
+++ b/src/Page.tsx
@@ -8,6 +8,9 @@ import { RouteDashboard } from "./routes/route-dashboard";
 import { RouteCertificateSecurity } from "./routes/route-certificate-security";
 import { RouteWorkspaceCreation } from "./routes/route-workspace-creation";
 import { RouteNotFound } from "./routes/route-not-found";
+import { RouteProvider } from "./routes/route-providers";
+import { RouteProviderCreate } from "./routes/route-provider-create";
+import { RouteProviderUpdate } from "./routes/route-provider-update";
 
 export default function Page() {
   return (
@@ -22,6 +25,15 @@ export default function Page() {
         path="/certificates/security"
         element={<RouteCertificateSecurity />}
       />
+
+      <Route path="providers">
+        <Route index element={<RouteProvider />} />
+        <Route element={<RouteProvider />}>
+          <Route path=":id" element={<RouteProviderUpdate />} />
+          <Route path="new" element={<RouteProviderCreate />} />
+        </Route>
+      </Route>
+
       <Route path="*" element={<RouteNotFound />} />
     </Routes>
   );
diff --git a/src/api/generated/types.gen.ts b/src/api/generated/types.gen.ts
index ec403510..2a202061 100644
--- a/src/api/generated/types.gen.ts
+++ b/src/api/generated/types.gen.ts
@@ -10,6 +10,19 @@ export type ActiveWorkspace = {
   last_updated: unknown;
 };
 
+/**
+ * Represents a request to add a provider endpoint.
+ */
+export type AddProviderEndpointRequest = {
+  id?: string | null;
+  name: string;
+  description?: string;
+  provider_type: ProviderType;
+  endpoint?: string;
+  auth_type?: ProviderAuthType | null;
+  api_key?: string | null;
+};
+
 /**
  * Represents an alert with it's respective conversation.
  */
@@ -100,7 +113,6 @@ export type ModelByProvider = {
  * Represents the different types of matchers we support.
  */
 export enum MuxMatcherType {
-  FILE_REGEX = "file_regex",
   CATCH_ALL = "catch_all",
 }
 
@@ -133,7 +145,7 @@ export type ProviderEndpoint = {
   name: string;
   description?: string;
   provider_type: ProviderType;
-  endpoint: string;
+  endpoint?: string;
   auth_type?: ProviderAuthType | null;
 };
 
@@ -219,7 +231,7 @@ export type V1ListProviderEndpointsResponse = Array<ProviderEndpoint>;
 export type V1ListProviderEndpointsError = HTTPValidationError;
 
 export type V1AddProviderEndpointData = {
-  body: ProviderEndpoint;
+  body: AddProviderEndpointRequest;
 };
 
 export type V1AddProviderEndpointResponse = ProviderEndpoint;
diff --git a/src/api/openapi.json b/src/api/openapi.json
index 67d67ecb..6536af0f 100644
--- a/src/api/openapi.json
+++ b/src/api/openapi.json
@@ -92,7 +92,7 @@
           "content": {
             "application/json": {
               "schema": {
-                "$ref": "#/components/schemas/ProviderEndpoint"
+                "$ref": "#/components/schemas/AddProviderEndpointRequest"
               }
             }
           }
@@ -1113,6 +1113,68 @@
         ],
         "title": "ActiveWorkspace"
       },
+      "AddProviderEndpointRequest": {
+        "properties": {
+          "id": {
+            "anyOf": [
+              {
+                "type": "string"
+              },
+              {
+                "type": "null"
+              }
+            ],
+            "title": "Id",
+            "default": ""
+          },
+          "name": {
+            "type": "string",
+            "title": "Name"
+          },
+          "description": {
+            "type": "string",
+            "title": "Description",
+            "default": ""
+          },
+          "provider_type": {
+            "$ref": "#/components/schemas/ProviderType"
+          },
+          "endpoint": {
+            "type": "string",
+            "title": "Endpoint",
+            "default": ""
+          },
+          "auth_type": {
+            "anyOf": [
+              {
+                "$ref": "#/components/schemas/ProviderAuthType"
+              },
+              {
+                "type": "null"
+              }
+            ],
+            "default": "none"
+          },
+          "api_key": {
+            "anyOf": [
+              {
+                "type": "string"
+              },
+              {
+                "type": "null"
+              }
+            ],
+            "title": "Api Key"
+          }
+        },
+        "type": "object",
+        "required": [
+          "name",
+          "provider_type"
+        ],
+        "title": "AddProviderEndpointRequest",
+        "description": "Represents a request to add a provider endpoint."
+      },
       "AlertConversation": {
         "properties": {
           "conversation": {
@@ -1437,7 +1499,6 @@
       "MuxMatcherType": {
         "type": "string",
         "enum": [
-          "file_regex",
           "catch_all"
         ],
         "title": "MuxMatcherType",
@@ -1515,7 +1576,8 @@
           },
           "endpoint": {
             "type": "string",
-            "title": "Endpoint"
+            "title": "Endpoint",
+            "default": ""
           },
           "auth_type": {
             "anyOf": [
@@ -1532,8 +1594,7 @@
         "type": "object",
         "required": [
           "name",
-          "provider_type",
-          "endpoint"
+          "provider_type"
         ],
         "title": "ProviderEndpoint",
         "description": "Represents a provider's endpoint configuration. This\nallows us to persist the configuration for each provider,\nso we can use this for muxing messages."
diff --git a/src/features/header/components/header.tsx b/src/features/header/components/header.tsx
index ce7f7c2e..19eaf836 100644
--- a/src/features/header/components/header.tsx
+++ b/src/features/header/components/header.tsx
@@ -3,9 +3,9 @@ import { SidebarTrigger } from "../../../components/ui/sidebar";
 import { DropdownMenu } from "../../../components/HoverPopover";
 import { Separator, ButtonDarkMode } from "@stacklok/ui-kit";
 import { WorkspacesSelection } from "@/features/workspace/components/workspaces-selection";
-import { CERTIFICATE_MENU_ITEMS } from "../constants/certificate-menu-items";
 import { HELP_MENU_ITEMS } from "../constants/help-menu-items";
 import { HeaderStatusMenu } from "./header-status-menu";
+import { SETTINGS_MENU_ITEMS } from "../constants/settings-menu-items";
 
 function HomeLink() {
   return (
@@ -39,8 +39,8 @@ export function Header({ hasError }: { hasError?: boolean }) {
       </div>
       <div className="flex items-center gap-1 mr-2">
         <HeaderStatusMenu />
-        <DropdownMenu title="Certificates" items={CERTIFICATE_MENU_ITEMS} />
         <DropdownMenu title="Help" items={HELP_MENU_ITEMS} />
+        <DropdownMenu title="Settings" items={SETTINGS_MENU_ITEMS} />
 
         <ButtonDarkMode />
       </div>
diff --git a/src/features/header/constants/certificate-menu-items.tsx b/src/features/header/constants/certificate-menu-items.tsx
deleted file mode 100644
index 64ce0b51..00000000
--- a/src/features/header/constants/certificate-menu-items.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { OptionsSchema } from "@stacklok/ui-kit";
-import { Download01, ShieldTick } from "@untitled-ui/icons-react";
-
-export const CERTIFICATE_MENU_ITEMS = [
-  {
-    icon: <ShieldTick />,
-    id: "about-certificate-security",
-    href: "/certificates/security",
-    textValue: "About certificate security",
-  },
-  {
-    icon: <Download01 />,
-    id: "download-certificates",
-    href: "/certificates",
-    textValue: "Download certificates",
-  },
-] as const satisfies OptionsSchema<"menu">[];
diff --git a/src/features/header/constants/settings-menu-items.tsx b/src/features/header/constants/settings-menu-items.tsx
new file mode 100644
index 00000000..f85a70f2
--- /dev/null
+++ b/src/features/header/constants/settings-menu-items.tsx
@@ -0,0 +1,39 @@
+import { OptionsSchema } from "@stacklok/ui-kit";
+import {
+  Download01,
+  LayersThree01,
+  ShieldTick,
+} from "@untitled-ui/icons-react";
+
+export const SETTINGS_MENU_ITEMS = [
+  {
+    textValue: "Providers",
+    id: "providers",
+    items: [
+      {
+        icon: <LayersThree01 />,
+        id: "providers",
+        href: "/providers",
+        textValue: "Providers",
+      },
+    ],
+  },
+  {
+    textValue: "Certificates",
+    id: "certificates",
+    items: [
+      {
+        icon: <ShieldTick />,
+        id: "about-certificate-security",
+        href: "/certificates/security",
+        textValue: "About certificate security",
+      },
+      {
+        icon: <Download01 />,
+        id: "download-certificates",
+        href: "/certificates",
+        textValue: "Download certificates",
+      },
+    ],
+  },
+] as const satisfies OptionsSchema<"menu">[];
diff --git a/src/features/providers/components/provider-dialog-footer.tsx b/src/features/providers/components/provider-dialog-footer.tsx
new file mode 100644
index 00000000..3297c601
--- /dev/null
+++ b/src/features/providers/components/provider-dialog-footer.tsx
@@ -0,0 +1,17 @@
+import { Button, DialogFooter } from "@stacklok/ui-kit";
+import { useNavigate } from "react-router-dom";
+
+export function ProviderDialogFooter() {
+  const navigate = useNavigate();
+
+  return (
+    <DialogFooter className="justify-end">
+      <Button variant="secondary" onPress={() => navigate("/providers")}>
+        Discard
+      </Button>
+      <Button type="submit" variant="primary">
+        Save
+      </Button>
+    </DialogFooter>
+  );
+}
diff --git a/src/features/providers/components/provider-dialog.tsx b/src/features/providers/components/provider-dialog.tsx
new file mode 100644
index 00000000..44ae7de9
--- /dev/null
+++ b/src/features/providers/components/provider-dialog.tsx
@@ -0,0 +1,39 @@
+import {
+  DialogModalOverlay,
+  DialogModal,
+  Dialog,
+  DialogHeader,
+  DialogTitle,
+  DialogCloseButton,
+} from "@stacklok/ui-kit";
+import { useNavigate } from "react-router-dom";
+
+export function ProviderDialog({
+  title,
+  children,
+}: {
+  title: string;
+  children: React.ReactNode;
+}) {
+  const navigate = useNavigate();
+
+  return (
+    <DialogModalOverlay
+      isDismissable={false}
+      isOpen
+      onOpenChange={() => {
+        navigate("/providers");
+      }}
+    >
+      <DialogModal>
+        <Dialog width="md" className="">
+          <DialogHeader>
+            <DialogTitle>{title}</DialogTitle>
+            <DialogCloseButton slot="close" />
+          </DialogHeader>
+          {children}
+        </Dialog>
+      </DialogModal>
+    </DialogModalOverlay>
+  );
+}
diff --git a/src/features/providers/components/provider-form.tsx b/src/features/providers/components/provider-form.tsx
new file mode 100644
index 00000000..0614af40
--- /dev/null
+++ b/src/features/providers/components/provider-form.tsx
@@ -0,0 +1,128 @@
+import { AddProviderEndpointRequest, ProviderAuthType } from "@/api/generated";
+import {
+  Label,
+  Select,
+  SelectButton,
+  Input,
+  TextField,
+} from "@stacklok/ui-kit";
+import {
+  getAuthTypeOptions,
+  getProviderType,
+  isProviderAuthType,
+  isProviderType,
+} from "../lib/utils";
+
+interface Props {
+  provider: AddProviderEndpointRequest;
+  setProvider: (provider: AddProviderEndpointRequest) => void;
+}
+
+export function ProviderForm({ provider, setProvider }: Props) {
+  return (
+    <div className="w-full">
+      <div className="">
+        <TextField
+          aria-label="Provider name"
+          name="name"
+          validationBehavior="aria"
+          isRequired
+          onChange={(name) => setProvider({ ...provider, name })}
+        >
+          <Label>Name</Label>
+          <Input value={provider.name} placeholder="Provider name" />
+        </TextField>
+      </div>
+      <div className="py-3">
+        <Label id="provider-type">Provider</Label>
+        <Select
+          aria-labelledby="provider type"
+          selectedKey={provider.provider_type}
+          name="provider_type"
+          isRequired
+          className="w-full"
+          placeholder="Select the provider type"
+          items={getProviderType()}
+          onSelectionChange={(provider_type) => {
+            if (isProviderType(provider_type)) {
+              setProvider({
+                ...provider,
+                provider_type,
+              });
+            }
+          }}
+        >
+          <SelectButton />
+        </Select>
+      </div>
+      <div className="py-3">
+        <TextField
+          aria-label="Provider description"
+          name="description"
+          validationBehavior="aria"
+          onChange={(description) => setProvider({ ...provider, description })}
+        >
+          <Label>Description (Optional)</Label>
+          <Input
+            placeholder="Provider description"
+            value={provider.description}
+          />
+        </TextField>
+      </div>
+      <div className="py-3">
+        <TextField
+          aria-label="Provider endpoint"
+          name="endpoint"
+          validationBehavior="aria"
+          isRequired
+          onChange={(endpoint) => setProvider({ ...provider, endpoint })}
+        >
+          <Label>Endpoint</Label>
+          <Input placeholder="Provider endpoint" value={provider.endpoint} />
+        </TextField>
+      </div>
+      <div className="py-3">
+        <Label id="provider-authentication">Authentication</Label>
+        <Select
+          aria-labelledby="provider auth type"
+          name="auth_type"
+          selectedKey={provider.auth_type}
+          isRequired
+          className="w-full"
+          placeholder="Select the authentication type"
+          items={getAuthTypeOptions()}
+          onSelectionChange={(auth_type) => {
+            if (isProviderAuthType(auth_type)) {
+              setProvider({ ...provider, auth_type });
+            }
+          }}
+        >
+          <SelectButton />
+        </Select>
+      </div>
+
+      {provider.auth_type === ProviderAuthType.API_KEY && (
+        <div className="pt-4">
+          <TextField
+            aria-label="Provider API key"
+            name="api_key"
+            type="password"
+            validationBehavior="aria"
+            isRequired
+            onChange={(api_key) => setProvider({ ...provider, api_key })}
+          >
+            <Label>Api key</Label>
+            <Input
+              placeholder={
+                provider.api_key === undefined
+                  ? "Update the provider API key"
+                  : "Specify the provider API key"
+              }
+              value={provider.api_key ?? ""}
+            />
+          </TextField>
+        </div>
+      )}
+    </div>
+  );
+}
diff --git a/src/features/providers/components/table-actions.tsx b/src/features/providers/components/table-actions.tsx
new file mode 100644
index 00000000..68c28fcc
--- /dev/null
+++ b/src/features/providers/components/table-actions.tsx
@@ -0,0 +1,53 @@
+import { ProviderEndpoint } from "@/api/generated";
+import {
+  MenuTrigger,
+  Button,
+  Popover,
+  Menu,
+  OptionsSchema,
+} from "@stacklok/ui-kit";
+import { DotsVertical, Settings04, Trash01 } from "@untitled-ui/icons-react";
+import { useMutationDeleteProvider } from "../hooks/use-mutation-delete-provider";
+import { useConfirmDeleteProvider } from "../hooks/use-confirm-delete-provider";
+
+const getProviderActions = ({
+  provider,
+  deleteProvider,
+}: {
+  provider: ProviderEndpoint;
+  deleteProvider: ReturnType<typeof useMutationDeleteProvider>["mutateAsync"];
+}): OptionsSchema<"menu">[] => [
+  {
+    textValue: "Edit",
+    icon: <Settings04 />,
+    id: "edit",
+    href: `/providers/${provider.id}`,
+  },
+  {
+    textValue: "Delete",
+    icon: <Trash01 />,
+    id: "delete",
+    onAction: () =>
+      deleteProvider({ path: { provider_id: provider.id as string } }),
+  },
+];
+
+export function TableActions({ provider }: { provider: ProviderEndpoint }) {
+  const deleteProvider = useConfirmDeleteProvider();
+
+  return (
+    <MenuTrigger>
+      <Button aria-label="Actions" isIcon variant="tertiary">
+        <DotsVertical />
+      </Button>
+      <Popover placement="bottom end">
+        <Menu
+          items={getProviderActions({
+            provider,
+            deleteProvider,
+          })}
+        />
+      </Popover>
+    </MenuTrigger>
+  );
+}
diff --git a/src/features/providers/components/table-providers.tsx b/src/features/providers/components/table-providers.tsx
new file mode 100644
index 00000000..197f5c37
--- /dev/null
+++ b/src/features/providers/components/table-providers.tsx
@@ -0,0 +1,114 @@
+import {
+  Badge,
+  Cell,
+  Column,
+  Row,
+  Table,
+  TableBody,
+  LinkButton,
+  TableHeader,
+  ResizableTableContainer,
+} from "@stacklok/ui-kit";
+import { Globe02, Tool01 } from "@untitled-ui/icons-react";
+import { PROVIDER_AUTH_TYPE_MAP } from "../lib/utils";
+import { TableActions } from "./table-actions";
+import { useProviders } from "../hooks/use-providers";
+import { match } from "ts-pattern";
+import { ComponentProps } from "react";
+import { ProviderEndpoint } from "@/api/generated";
+
+const COLUMN_MAP = {
+  provider: "provider",
+  type: "type",
+  endpoint: "endpoint",
+  auth: "auth",
+  configuration: "configuration",
+} as const;
+
+type ColumnId = keyof typeof COLUMN_MAP;
+type Column = { id: ColumnId } & Omit<ComponentProps<typeof Column>, "id">;
+const COLUMNS: Column[] = [
+  {
+    id: "provider",
+    isRowHeader: true,
+    children: "Name & Description",
+    width: "40%",
+  },
+  { id: "type", children: "Provider", width: "10%", className: "capitalize" },
+  { id: "endpoint", children: "Endpoint", width: "20%" },
+  { id: "auth", children: "Authentication", width: "20%" },
+  { id: "configuration", alignment: "end", width: "10%", children: "" },
+];
+
+function CellRenderer({
+  column,
+  row,
+}: {
+  column: Column;
+  row: ProviderEndpoint;
+}) {
+  return match(column.id)
+    .with(COLUMN_MAP.provider, () => (
+      <>
+        <div className="text-primary">{row.name}</div>
+        <div className="text-tertiary">{row.description}</div>
+      </>
+    ))
+    .with(COLUMN_MAP.type, () => row.provider_type)
+    .with(COLUMN_MAP.endpoint, () => (
+      <div className="flex items-center gap-2">
+        <Globe02 className="size-4" />
+        <span>{row.endpoint}</span>
+      </div>
+    ))
+    .with(COLUMN_MAP.auth, () => (
+      <div className="flex items-center justify-between gap-2">
+        {row.auth_type ? (
+          <Badge size="sm" className="text-tertiary">
+            {PROVIDER_AUTH_TYPE_MAP[row.auth_type]}
+          </Badge>
+        ) : (
+          "N/A"
+        )}
+        <LinkButton
+          variant="tertiary"
+          className="flex gap-2 items-center"
+          href={`/providers/${row.id}`}
+        >
+          <Tool01 className="size-4" /> Manage
+        </LinkButton>
+      </div>
+    ))
+    .with(COLUMN_MAP.configuration, () => <TableActions provider={row} />)
+    .exhaustive();
+}
+
+export function TableProviders() {
+  const { data: providers = [] } = useProviders();
+
+  return (
+    <ResizableTableContainer>
+      <Table aria-label="List of workspaces">
+        <TableHeader columns={COLUMNS}>
+          {(column) => <Column {...column} id={column.id} />}
+        </TableHeader>
+
+        <TableBody items={providers}>
+          {(row) => (
+            <Row columns={COLUMNS} id={`${row.id}`}>
+              {(column) => (
+                <Cell
+                  className="h-6 group-last/row:border-b-0"
+                  id={column.id}
+                  alignment={column.alignment}
+                >
+                  <CellRenderer column={column} row={row} />
+                </Cell>
+              )}
+            </Row>
+          )}
+        </TableBody>
+      </Table>
+    </ResizableTableContainer>
+  );
+}
diff --git a/src/features/providers/hooks/use-confirm-delete-provider.tsx b/src/features/providers/hooks/use-confirm-delete-provider.tsx
new file mode 100644
index 00000000..ac6ad6b6
--- /dev/null
+++ b/src/features/providers/hooks/use-confirm-delete-provider.tsx
@@ -0,0 +1,33 @@
+import { useConfirm } from "@/hooks/use-confirm";
+import { useCallback } from "react";
+import { useMutationDeleteProvider } from "./use-mutation-delete-provider";
+
+export function useConfirmDeleteProvider() {
+  const { mutateAsync: deleteProvider } = useMutationDeleteProvider();
+
+  const { confirm } = useConfirm();
+
+  return useCallback(
+    async (...params: Parameters<typeof deleteProvider>) => {
+      const answer = await confirm(
+        <>
+          <p className="mb-1">
+            Are you sure you want to permanently delete this provider?
+          </p>
+        </>,
+        {
+          buttons: {
+            yes: "Delete",
+            no: "Cancel",
+          },
+          title: "Permanently delete provider",
+          isDestructive: true,
+        }
+      );
+      if (answer) {
+        return deleteProvider(...params);
+      }
+    },
+    [confirm, deleteProvider]
+  );
+}
diff --git a/src/features/providers/hooks/use-invalidate-providers-queries.ts b/src/features/providers/hooks/use-invalidate-providers-queries.ts
new file mode 100644
index 00000000..36aee59c
--- /dev/null
+++ b/src/features/providers/hooks/use-invalidate-providers-queries.ts
@@ -0,0 +1,16 @@
+import { v1ListProviderEndpointsQueryKey } from "@/api/generated/@tanstack/react-query.gen";
+import { useQueryClient } from "@tanstack/react-query";
+import { useCallback } from "react";
+
+export function useInvalidateProvidersQueries() {
+  const queryClient = useQueryClient();
+
+  const invalidate = useCallback(async () => {
+    await queryClient.invalidateQueries({
+      queryKey: v1ListProviderEndpointsQueryKey(),
+      refetchType: "all",
+    });
+  }, [queryClient]);
+
+  return invalidate;
+}
diff --git a/src/features/providers/hooks/use-mutation-create-provider.ts b/src/features/providers/hooks/use-mutation-create-provider.ts
new file mode 100644
index 00000000..b420d940
--- /dev/null
+++ b/src/features/providers/hooks/use-mutation-create-provider.ts
@@ -0,0 +1,16 @@
+import { v1AddProviderEndpointMutation } from "@/api/generated/@tanstack/react-query.gen";
+import { useToastMutation } from "@/hooks/use-toast-mutation";
+import { useInvalidateProvidersQueries } from "./use-invalidate-providers-queries";
+import { useNavigate } from "react-router-dom";
+
+export function useMutationCreateProvider() {
+  const navigate = useNavigate();
+  const invalidate = useInvalidateProvidersQueries();
+  return useToastMutation({
+    ...v1AddProviderEndpointMutation(),
+    onSuccess: async () => {
+      await invalidate();
+      navigate("/providers");
+    },
+  });
+}
diff --git a/src/features/providers/hooks/use-mutation-delete-provider.ts b/src/features/providers/hooks/use-mutation-delete-provider.ts
new file mode 100644
index 00000000..6dacf200
--- /dev/null
+++ b/src/features/providers/hooks/use-mutation-delete-provider.ts
@@ -0,0 +1,13 @@
+import { useToastMutation } from "@/hooks/use-toast-mutation";
+import { useInvalidateProvidersQueries } from "./use-invalidate-providers-queries";
+import { v1DeleteProviderEndpointMutation } from "@/api/generated/@tanstack/react-query.gen";
+
+export const useMutationDeleteProvider = () => {
+  const invalidate = useInvalidateProvidersQueries();
+
+  return useToastMutation({
+    ...v1DeleteProviderEndpointMutation(),
+    onSuccess: () => invalidate(),
+    successMsg: () => "Successfully deleted provider",
+  });
+};
diff --git a/src/features/providers/hooks/use-mutation-update-provider.ts b/src/features/providers/hooks/use-mutation-update-provider.ts
new file mode 100644
index 00000000..b8c987bf
--- /dev/null
+++ b/src/features/providers/hooks/use-mutation-update-provider.ts
@@ -0,0 +1,43 @@
+import { useToastMutation } from "@/hooks/use-toast-mutation";
+import { useNavigate } from "react-router-dom";
+import { useInvalidateProvidersQueries } from "./use-invalidate-providers-queries";
+import {
+  AddProviderEndpointRequest,
+  ProviderAuthType,
+  v1ConfigureAuthMaterial,
+  v1UpdateProviderEndpoint,
+} from "@/api/generated";
+
+export function useMutationUpdateProvider() {
+  const navigate = useNavigate();
+  const invalidate = useInvalidateProvidersQueries();
+
+  const mutationFn = ({ api_key, ...rest }: AddProviderEndpointRequest) => {
+    const provider_id = rest.id;
+    if (!provider_id) throw new Error("Provider is missing");
+    return Promise.all([
+      v1ConfigureAuthMaterial({
+        path: { provider_id },
+        body: {
+          api_key: api_key,
+          auth_type: rest.auth_type as ProviderAuthType,
+        },
+        throwOnError: true,
+      }),
+
+      v1UpdateProviderEndpoint({
+        path: { provider_id },
+        body: rest,
+      }),
+    ]);
+  };
+
+  return useToastMutation({
+    mutationFn,
+    successMsg: "Successfully updated provider",
+    onSuccess: async () => {
+      await invalidate();
+      navigate("/providers");
+    },
+  });
+}
diff --git a/src/features/providers/hooks/use-provider.ts b/src/features/providers/hooks/use-provider.ts
new file mode 100644
index 00000000..87e5044c
--- /dev/null
+++ b/src/features/providers/hooks/use-provider.ts
@@ -0,0 +1,27 @@
+import { useQuery } from "@tanstack/react-query";
+import { v1GetProviderEndpointOptions } from "@/api/generated/@tanstack/react-query.gen";
+import { AddProviderEndpointRequest, ProviderType } from "@/api/generated";
+import { useEffect, useState } from "react";
+
+export function useProvider(providerId: string) {
+  const [provider, setProvider] = useState<AddProviderEndpointRequest>({
+    name: "",
+    description: "",
+    auth_type: null,
+    provider_type: ProviderType.OPENAI,
+    endpoint: "",
+    api_key: "",
+  });
+
+  const { data, isPending, isError } = useQuery({
+    ...v1GetProviderEndpointOptions({ path: { provider_id: providerId } }),
+  });
+
+  useEffect(() => {
+    if (data) {
+      setProvider(data);
+    }
+  }, [data]);
+
+  return { isPending, isError, provider, setProvider };
+}
diff --git a/src/features/providers/hooks/use-providers.ts b/src/features/providers/hooks/use-providers.ts
new file mode 100644
index 00000000..cf112f09
--- /dev/null
+++ b/src/features/providers/hooks/use-providers.ts
@@ -0,0 +1,11 @@
+import { useQuery } from "@tanstack/react-query";
+import { v1ListProviderEndpointsOptions } from "@/api/generated/@tanstack/react-query.gen";
+
+export function useProviders() {
+  return useQuery({
+    ...v1ListProviderEndpointsOptions(),
+    refetchOnMount: true,
+    refetchOnReconnect: true,
+    refetchOnWindowFocus: true,
+  });
+}
diff --git a/src/features/providers/lib/utils.ts b/src/features/providers/lib/utils.ts
new file mode 100644
index 00000000..80a2e7df
--- /dev/null
+++ b/src/features/providers/lib/utils.ts
@@ -0,0 +1,29 @@
+import { ProviderAuthType, ProviderType } from "@/api/generated";
+
+export const PROVIDER_AUTH_TYPE_MAP = {
+  [ProviderAuthType.NONE]: "None",
+  [ProviderAuthType.PASSTHROUGH]: "Passthrough",
+  [ProviderAuthType.API_KEY]: "API Key",
+};
+
+export function getAuthTypeOptions() {
+  return Object.entries(PROVIDER_AUTH_TYPE_MAP).map(([id, textValue]) => ({
+    id,
+    textValue,
+  }));
+}
+
+export function getProviderType() {
+  return Object.values(ProviderType).map((textValue) => ({
+    id: textValue,
+    textValue,
+  }));
+}
+
+export function isProviderType(value: unknown): value is ProviderType {
+  return Object.values(ProviderType).includes(value as ProviderType);
+}
+
+export function isProviderAuthType(value: unknown): value is ProviderAuthType {
+  return Object.values(ProviderAuthType).includes(value as ProviderAuthType);
+}
diff --git a/src/features/workspace/components/workspace-preferred-model.tsx b/src/features/workspace/components/workspace-preferred-model.tsx
index f3a1c75b..47747732 100644
--- a/src/features/workspace/components/workspace-preferred-model.tsx
+++ b/src/features/workspace/components/workspace-preferred-model.tsx
@@ -12,7 +12,7 @@ import { MuxMatcherType } from "@/api/generated";
 import { FormEvent } from "react";
 import { usePreferredModelWorkspace } from "../hooks/use-preferred-preferred-model";
 import { Select, SelectButton } from "@stacklok/ui-kit";
-import { useModelsData } from "@/hooks/useModelsData";
+import { useModelsData } from "@/hooks/use-models-data";
 
 export function WorkspacePreferredModel({
   className,
@@ -23,7 +23,8 @@ export function WorkspacePreferredModel({
   workspaceName: string;
   isArchived: boolean | undefined;
 }) {
-  const { preferredModel, setPreferredModel } = usePreferredModelWorkspace();
+  const { preferredModel, setPreferredModel } =
+    usePreferredModelWorkspace(workspaceName);
   const { mutateAsync } = useMutationPreferredModelWorkspace();
   const { data: providerModels = [] } = useModelsData();
   const { model, provider_id } = preferredModel;
diff --git a/src/features/workspace/hooks/use-preferred-preferred-model.ts b/src/features/workspace/hooks/use-preferred-preferred-model.ts
index 555a2a23..834a2ded 100644
--- a/src/features/workspace/hooks/use-preferred-preferred-model.ts
+++ b/src/features/workspace/hooks/use-preferred-preferred-model.ts
@@ -1,4 +1,7 @@
-import { MuxRule } from "@/api/generated";
+import { MuxRule, V1GetWorkspaceMuxesData } from "@/api/generated";
+import { v1GetWorkspaceMuxesOptions } from "@/api/generated/@tanstack/react-query.gen";
+import { useQuery } from "@tanstack/react-query";
+import { useEffect, useMemo } from "react";
 import { create } from "zustand";
 
 export type ModelRule = Omit<MuxRule, "matcher_type" | "matcher"> & {};
@@ -8,7 +11,7 @@ type State = {
   preferredModel: ModelRule;
 };
 
-export const usePreferredModelWorkspace = create<State>((set) => ({
+const useModelValue = create<State>((set) => ({
   preferredModel: {
     provider_id: "",
     model: "",
@@ -17,3 +20,33 @@ export const usePreferredModelWorkspace = create<State>((set) => ({
     set({ preferredModel: { provider_id, model } });
   },
 }));
+
+const usePreferredModel = (options: {
+  path: {
+    workspace_name: string;
+  };
+}) => {
+  return useQuery({
+    ...v1GetWorkspaceMuxesOptions(options),
+  });
+};
+export const usePreferredModelWorkspace = (workspaceName: string) => {
+  const options: V1GetWorkspaceMuxesData &
+    Omit<V1GetWorkspaceMuxesData, "body"> = useMemo(
+    () => ({
+      path: { workspace_name: workspaceName },
+    }),
+    [workspaceName],
+  );
+  const { data } = usePreferredModel(options);
+  const { preferredModel, setPreferredModel } = useModelValue();
+
+  useEffect(() => {
+    const providerModel = data?.[0];
+    if (providerModel) {
+      setPreferredModel(providerModel);
+    }
+  }, [data, setPreferredModel]);
+
+  return { preferredModel, setPreferredModel };
+};
diff --git a/src/hooks/use-models-data.ts b/src/hooks/use-models-data.ts
new file mode 100644
index 00000000..c423147c
--- /dev/null
+++ b/src/hooks/use-models-data.ts
@@ -0,0 +1,11 @@
+import { useQuery } from "@tanstack/react-query";
+import { v1ListAllModelsForAllProvidersOptions } from "@/api/generated/@tanstack/react-query.gen";
+
+export const useModelsData = () => {
+  return useQuery({
+    ...v1ListAllModelsForAllProvidersOptions(),
+    refetchOnMount: true,
+    refetchOnReconnect: true,
+    refetchOnWindowFocus: true,
+  });
+};
diff --git a/src/hooks/use-toast-mutation.ts b/src/hooks/use-toast-mutation.ts
index 29b8686a..e9b935ce 100644
--- a/src/hooks/use-toast-mutation.ts
+++ b/src/hooks/use-toast-mutation.ts
@@ -1,3 +1,4 @@
+import { HTTPValidationError } from "@/api/generated";
 import { toast } from "@stacklok/ui-kit";
 import {
   DefaultError,
@@ -31,7 +32,7 @@ export function useToastMutation<
   } = useMutation(options);
 
   const mutateAsync = useCallback(
-    async <TError extends { detail: string | undefined }>(
+    async <TError extends { detail: string | undefined | HTTPValidationError }>(
       variables: Parameters<typeof originalMutateAsync>[0],
       options: Parameters<typeof originalMutateAsync>[1] = {},
     ) => {
@@ -41,8 +42,24 @@ export function useToastMutation<
         success:
           typeof successMsg === "function" ? successMsg(variables) : successMsg,
         loading: loadingMsg ?? "Loading...",
-        error: (e: TError) =>
-          errorMsg ?? (e.detail ? e.detail : "An error occurred"),
+        error: (e: TError) => {
+          if (errorMsg) return errorMsg;
+
+          if (typeof e.detail == "string") {
+            return e.detail ?? "An error occurred";
+          }
+
+          if (Array.isArray(e.detail)) {
+            const err = e.detail
+              ?.map((item) => `${item.msg} - ${JSON.stringify(item.loc)}`)
+              .filter(Boolean)
+              .join(", ");
+
+            return err ?? "An error occurred";
+          }
+
+          return "An error occurred";
+        },
       });
     },
     [errorMsg, loadingMsg, originalMutateAsync, successMsg],
diff --git a/src/hooks/useModelsData.ts b/src/hooks/useModelsData.ts
deleted file mode 100644
index b58fda4e..00000000
--- a/src/hooks/useModelsData.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { useQuery } from "@tanstack/react-query";
-import { v1ListAllModelsForAllProvidersOptions } from "@/api/generated/@tanstack/react-query.gen";
-import { V1ListAllModelsForAllProvidersResponse } from "@/api/generated";
-
-export const useModelsData = () => {
-  return useQuery({
-    ...v1ListAllModelsForAllProvidersOptions(),
-    queryFn: async () => {
-      const response: V1ListAllModelsForAllProvidersResponse = [
-        {
-          name: "claude-3.5",
-          provider_name: "anthropic",
-          provider_id: "anthropic",
-        },
-        {
-          name: "claude-3.6",
-          provider_name: "anthropic",
-          provider_id: "anthropic",
-        },
-        {
-          name: "claude-3.7",
-          provider_name: "anthropic",
-          provider_id: "anthropic",
-        },
-        { name: "chatgpt-4o", provider_name: "openai", provider_id: "openai" },
-        { name: "chatgpt-4p", provider_name: "openai", provider_id: "openai" },
-      ];
-
-      return response;
-    },
-  });
-};
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index 07a499a6..976388a4 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -73,7 +73,7 @@ export function groupPromptsByRelativeDate(prompts: Conversation[]) {
   const promptsSorted = prompts.sort(
     (a, b) =>
       new Date(b.conversation_timestamp).getTime() -
-      new Date(a.conversation_timestamp).getTime(),
+      new Date(a.conversation_timestamp).getTime()
   );
 
   const grouped = promptsSorted.reduce(
@@ -90,7 +90,7 @@ export function groupPromptsByRelativeDate(prompts: Conversation[]) {
       (groups[group] ?? []).push(prompt);
       return groups;
     },
-    {} as Record<string, Conversation[]>,
+    {} as Record<string, Conversation[]>
   );
 
   return grouped;
@@ -125,10 +125,15 @@ export function sanitizeQuestionPrompt({
 }
 
 export function getIssueDetectedType(
-  alert: AlertConversation,
+  alert: AlertConversation
 ): "malicious_package" | "leaked_secret" | null {
   if (isAlertMalicious(alert)) return "malicious_package";
   if (isAlertSecret(alert)) return "leaked_secret";
 
   return null;
 }
+
+export function capitalize(text: string) {
+  const [first, ...rest] = text;
+  return first ? first.toUpperCase() + rest.join("") : text;
+}
diff --git a/src/main.tsx b/src/main.tsx
index 4a874faf..7398fed0 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -27,6 +27,7 @@ createRoot(document.getElementById("root")!).render(
           <SidebarProvider>
             <QueryClientProvider>
               <ErrorBoundary fallback={<Error />}>
+                <ReactQueryDevtools />
                 <ConfirmProvider>
                   <Toaster />
                   <App />
diff --git a/src/mocks/msw/fixtures/GET_PROVIDERS_MODELS.json b/src/mocks/msw/fixtures/GET_PROVIDERS_MODELS.json
new file mode 100644
index 00000000..1a6ff4dd
--- /dev/null
+++ b/src/mocks/msw/fixtures/GET_PROVIDERS_MODELS.json
@@ -0,0 +1,27 @@
+[
+    {
+        "name": "claude-3.5",
+        "provider_id": "id_1",
+        "provider_name": "anthropic"
+    },
+    {
+        "name": "claude-3.6",
+        "provider_id": "id_2",
+        "provider_name": "anthropic"
+    },
+    {
+        "name": "claude-3.7",
+        "provider_id": "id_3",
+        "provider_name": "anthropic"
+    },
+    {
+        "name": "chatgpt-4o",
+        "provider_id": "id_4",
+        "provider_name": "openai"
+    },
+    {
+        "name": "chatgpt-4p",
+        "provider_id": "id_5",
+        "provider_name": "openai"
+    }
+]
\ No newline at end of file
diff --git a/src/mocks/msw/handlers.ts b/src/mocks/msw/handlers.ts
index be2bd963..2a4974cb 100644
--- a/src/mocks/msw/handlers.ts
+++ b/src/mocks/msw/handlers.ts
@@ -3,6 +3,7 @@ import mockedPrompts from "@/mocks/msw/fixtures/GET_MESSAGES.json";
 import mockedAlerts from "@/mocks/msw/fixtures/GET_ALERTS.json";
 import mockedWorkspaces from "@/mocks/msw/fixtures/GET_WORKSPACES.json";
 import mockedProviders from "@/mocks/msw/fixtures/GET_PROVIDERS.json";
+import mockedProvidersModels from "@/mocks/msw/fixtures/GET_PROVIDERS_MODELS.json";
 import { ProviderType } from "@/api/generated";
 
 export const handlers = [
@@ -112,12 +113,18 @@ export const handlers = [
     "*/api/v1/workspaces/:workspace_name/muxes",
     () => new HttpResponse(null, { status: 204 }),
   ),
-  http.get("*/api/v1/provider-endpoints", () =>
-    HttpResponse.json(mockedProviders),
+  http.get("*/api/v1/provider-endpoints/:provider_name/models", () =>
+    HttpResponse.json(mockedProvidersModels),
+  ),
+  http.get("*/api/v1/provider-endpoints/models", () =>
+    HttpResponse.json(mockedProvidersModels),
   ),
   http.get("*/api/v1/provider-endpoints/:provider_id", () =>
     HttpResponse.json(mockedProviders[0]),
   ),
+  http.get("*/api/v1/provider-endpoints", () =>
+    HttpResponse.json(mockedProviders),
+  ),
   http.post(
     "*/api/v1/provider-endpoints",
     () => new HttpResponse(null, { status: 204 }),
@@ -130,22 +137,4 @@ export const handlers = [
     "*/api/v1/provider-endpoints",
     () => new HttpResponse(null, { status: 204 }),
   ),
-  http.get("*/api/v1/provider-endpoints/:provider_name/models", () =>
-    HttpResponse.json([
-      { name: "claude-3.5", provider: "anthropic" },
-      { name: "claude-3.6", provider: "anthropic" },
-      { name: "claude-3.7", provider: "anthropic" },
-      { name: "chatgpt-4o", provider: "openai" },
-      { name: "chatgpt-4p", provider: "openai" },
-    ]),
-  ),
-  http.get("*/api/v1/provider-endpoints/models", () =>
-    HttpResponse.json([
-      { name: "claude-3.5", provider: "anthropic" },
-      { name: "claude-3.6", provider: "anthropic" },
-      { name: "claude-3.7", provider: "anthropic" },
-      { name: "chatgpt-4o", provider: "openai" },
-      { name: "chatgpt-4p", provider: "openai" },
-    ]),
-  ),
 ];
diff --git a/src/routes/route-provider-create.tsx b/src/routes/route-provider-create.tsx
new file mode 100644
index 00000000..197e5401
--- /dev/null
+++ b/src/routes/route-provider-create.tsx
@@ -0,0 +1,41 @@
+import { AddProviderEndpointRequest, ProviderType } from "@/api/generated";
+import { ProviderDialog } from "@/features/providers/components/provider-dialog";
+import { ProviderDialogFooter } from "@/features/providers/components/provider-dialog-footer";
+import { ProviderForm } from "@/features/providers/components/provider-form";
+import { useMutationCreateProvider } from "@/features/providers/hooks/use-mutation-create-provider";
+import { DialogContent, Form } from "@stacklok/ui-kit";
+import { useState } from "react";
+
+const DEFAULT_PROVIDER_STATE = {
+  name: "",
+  description: "",
+  auth_type: null,
+  provider_type: ProviderType.OPENAI,
+  endpoint: "",
+  api_key: "",
+};
+
+export function RouteProviderCreate() {
+  const [provider, setProvider] = useState<AddProviderEndpointRequest>(
+    DEFAULT_PROVIDER_STATE,
+  );
+  const { mutateAsync } = useMutationCreateProvider();
+
+  const handleSubmit = (event: React.FormEvent) => {
+    event.preventDefault();
+    mutateAsync({
+      body: provider,
+    });
+  };
+
+  return (
+    <Form onSubmit={handleSubmit} validationBehavior="aria">
+      <ProviderDialog title="Create Provider">
+        <DialogContent className="p-8">
+          <ProviderForm provider={provider} setProvider={setProvider} />
+        </DialogContent>
+        <ProviderDialogFooter />
+      </ProviderDialog>
+    </Form>
+  );
+}
diff --git a/src/routes/route-provider-update.tsx b/src/routes/route-provider-update.tsx
new file mode 100644
index 00000000..209ac483
--- /dev/null
+++ b/src/routes/route-provider-update.tsx
@@ -0,0 +1,35 @@
+import { ProviderDialog } from "@/features/providers/components/provider-dialog";
+import { ProviderDialogFooter } from "@/features/providers/components/provider-dialog-footer";
+import { ProviderForm } from "@/features/providers/components/provider-form";
+import { useMutationUpdateProvider } from "@/features/providers/hooks/use-mutation-update-provider";
+import { useProvider } from "@/features/providers/hooks/use-provider";
+import { DialogContent, Form } from "@stacklok/ui-kit";
+import { useParams } from "react-router-dom";
+
+export function RouteProviderUpdate() {
+  const { id } = useParams();
+  if (id === undefined) {
+    throw new Error("Provider id is required");
+  }
+  const { setProvider, provider } = useProvider(id);
+  const { mutateAsync } = useMutationUpdateProvider();
+
+  const handleSubmit = (event: React.FormEvent) => {
+    event.preventDefault();
+    mutateAsync(provider);
+  };
+
+  // TODO add empty state and loading in a next step
+  if (provider === undefined) return;
+
+  return (
+    <ProviderDialog title="Manage Provider">
+      <Form onSubmit={handleSubmit} validationBehavior="aria">
+        <DialogContent className="p-8">
+          <ProviderForm provider={provider} setProvider={setProvider} />
+        </DialogContent>
+        <ProviderDialogFooter />
+      </Form>
+    </ProviderDialog>
+  );
+}
diff --git a/src/routes/route-providers.tsx b/src/routes/route-providers.tsx
new file mode 100644
index 00000000..7ea1aeea
--- /dev/null
+++ b/src/routes/route-providers.tsx
@@ -0,0 +1,37 @@
+import { BreadcrumbHome } from "@/components/BreadcrumbHome";
+import {
+  Breadcrumbs,
+  Breadcrumb,
+  Heading,
+  Card,
+  LinkButton,
+  CardBody,
+} from "@stacklok/ui-kit";
+import { twMerge } from "tailwind-merge";
+import { PlusSquare } from "@untitled-ui/icons-react";
+import { TableProviders } from "@/features/providers/components/table-providers";
+import { Outlet } from "react-router-dom";
+
+export function RouteProvider({ className }: { className?: string }) {
+  return (
+    <>
+      <Breadcrumbs>
+        <BreadcrumbHome />
+        <Breadcrumb>Providers</Breadcrumb>
+      </Breadcrumbs>
+      <Heading level={4} className="mb-4 flex items-center justify-between">
+        Providers
+        <LinkButton className="w-fit" href="/providers/new">
+          <PlusSquare /> Add Provider
+        </LinkButton>
+      </Heading>
+      <Card className={twMerge(className, "shrink-0")}>
+        <CardBody>
+          <TableProviders />
+        </CardBody>
+      </Card>
+
+      <Outlet />
+    </>
+  );
+}
diff --git a/src/routes/route-workspace.tsx b/src/routes/route-workspace.tsx
index ba265320..b42ddd24 100644
--- a/src/routes/route-workspace.tsx
+++ b/src/routes/route-workspace.tsx
@@ -54,7 +54,7 @@ export function RouteWorkspace() {
         workspaceName={name}
       />
       <WorkspacePreferredModel
-        className="mb-4 hidden"
+        className="mb-4"
         isArchived={isArchived}
         workspaceName={name}
       />