"use client" import { dmSans125ClassName } from "@/lib/fonts" import { formatRelativeTime } from "@/components/settings/sync-utils" import { cn } from "@lib/utils" import { useAuth } from "@lib/auth-context" import { authClient } from "@lib/auth" import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@ui/components/alert-dialog" import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@ui/components/dialog" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@ui/components/select" import * as DialogPrimitive from "@radix-ui/react-dialog" import { PillButton } from "../integrations/install-steps" import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" import { Check, Copy, KeyRound, Loader2, Plus, Trash2, XIcon, } from "lucide-react" import { useCallback, useId, useState } from "react" import { toast } from "sonner" type ListedApiKey = { id: string name: string | null start: string | null key?: string | null createdAt: string expiresAt: string | null lastRequest: string | null enabled: boolean isScoped: boolean containerTags: string[] | null smType: string | null smClient: string | null } const EXPIRY_OPTIONS = [ { label: "1 year", value: "365" }, { label: "6 months", value: "180" }, { label: "30 days", value: "30" }, { label: "7 days", value: "7" }, { label: "Never", value: "0" }, ] as const const API_URL = process.env.NEXT_PUBLIC_BACKEND_URL ?? "https://api.supermemory.ai" const MODAL_SHADOW = "0 2.842px 14.211px 0 rgba(0, 0, 0, 0.25), 0.711px 0.711px 0.711px 0 rgba(255, 255, 255, 0.10) inset" const pillInputClass = "h-9 w-full rounded-full border border-[#1E293B] bg-[#0D121A] px-3.5 text-[13px] font-medium text-[#FAFAFA] outline-none placeholder:text-[#5F6673] focus:border-[#334155]" function ModalClose() { return ( Close ) } function SettingsCard({ children }: { children: React.ReactNode }) { return (
{children}
) } function formatKeyPreview(start: string | null | undefined): string { if (!start) return "sm_••••••" return `${start}••••••` } function isExpired(expiresAt: string | null): boolean { if (!expiresAt) return false return new Date(expiresAt).getTime() <= Date.now() } function formatExpiresLabel(expiresAt: string | null): string { if (!expiresAt) return "Never" const date = new Date(expiresAt) if (Number.isNaN(date.getTime())) return "—" if (date.getTime() <= Date.now()) return "Expired" return date.toLocaleDateString() } function extractCreatedKey(result: unknown): string { if (!result || typeof result !== "object") { throw new Error("API key missing from response") } const r = result as { key?: string data?: { key?: string } error?: { message?: string } } if (r.error?.message) throw new Error(r.error.message) const key = r.key ?? r.data?.key if (!key) throw new Error("API key missing from response") return key } export default function ApiKeys({ dialogPortalContainer, }: { dialogPortalContainer?: HTMLElement | null }) { const { org } = useAuth() const queryClient = useQueryClient() const nameId = useId() const [createOpen, setCreateOpen] = useState(false) const [keyName, setKeyName] = useState("") const [expiryDays, setExpiryDays] = useState("365") const [createdKey, setCreatedKey] = useState(null) const [copied, setCopied] = useState(false) const [revokeTarget, setRevokeTarget] = useState(null) const { data: keys = [], isLoading, isError, refetch, } = useQuery({ queryKey: ["api-keys", org?.id, "manage"], queryFn: async () => { if (!org?.id) return [] const res = await fetch(`${API_URL}/v3/auth/keys?type=keys`, { credentials: "include", }) if (!res.ok) { throw new Error("Failed to load API keys") } const data = (await res.json()) as { keys?: ListedApiKey[] } return data.keys ?? [] }, enabled: !!org?.id, staleTime: 30 * 1000, }) const invalidateKeys = useCallback(() => { queryClient.invalidateQueries({ queryKey: ["api-keys", org?.id] }) queryClient.invalidateQueries({ queryKey: ["api-keys", org?.id, "manage"] }) }, [org?.id, queryClient]) const createKeyMutation = useMutation({ mutationFn: async () => { if (!org?.id) throw new Error("Organization is required") const days = Number(expiryDays) const expiresIn = days > 0 ? days * 24 * 60 * 60 : undefined const name = keyName.trim() || `key-${new Date().toISOString().slice(0, 10)}` const res = await authClient.apiKey.create({ name, expiresIn, metadata: { organizationId: org.id }, prefix: `sm_${org.id}_`, }) return extractCreatedKey(res) }, onSuccess: (key) => { setCreatedKey(key) setKeyName("") setExpiryDays("365") setCopied(false) invalidateKeys() toast.success("API key created") }, onError: (error) => { toast.error("Failed to create API key", { description: error instanceof Error ? error.message : "Unknown error", }) }, }) const revokeKeyMutation = useMutation({ mutationFn: async (keyId: string) => { const res = await authClient.apiKey.delete({ keyId }) if (res && typeof res === "object" && "error" in res && res.error) { const err = res.error as { message?: string } throw new Error(err.message ?? "Failed to revoke API key") } }, onSuccess: () => { setRevokeTarget(null) invalidateKeys() toast.success("API key revoked") }, onError: (error) => { toast.error("Failed to revoke API key", { description: error instanceof Error ? error.message : "Unknown error", }) }, }) const handleCopy = async (value: string) => { try { await navigator.clipboard.writeText(value) setCopied(true) toast.success("API key copied to clipboard") setTimeout(() => setCopied(false), 2000) } catch { toast.error("Failed to copy API key") } } const resetCreateState = () => { setCreateOpen(false) setCreatedKey(null) setKeyName("") setExpiryDays("365") setCopied(false) } const handleCreateOpenChange = (open: boolean) => { if (!open) { resetCreateState() return } setCreateOpen(true) } return (

API Keys

Create keys for the Supermemory API, SDKs, and custom integrations. Keys are shown once at creation.

{ setCreatedKey(null) setCreateOpen(true) }} > Create key
{isLoading ? (
Loading keys…
) : isError ? (

Couldn't load API keys.

) : keys.length === 0 ? (

No API keys yet

Create your first key to use the API programmatically or connect custom tools.

{ setCreatedKey(null) setCreateOpen(true) }} > Create key
) : (
    {keys.map((key) => { const expired = isExpired(key.expiresAt) const disabled = key.enabled === false || expired return (
  • {key.name?.trim() || "Unnamed key"}

    {key.isScoped && ( Scoped )} {disabled && ( {expired ? "Expired" : "Disabled"} )}
    {formatKeyPreview(key.start ?? key.key)} · Created {formatRelativeTime(key.createdAt)} · Last used{" "} {key.lastRequest ? formatRelativeTime(key.lastRequest) : "never"} · Expires {formatExpiresLabel(key.expiresAt)}
  • ) })}
)}

Need docs?{" "} API quickstart {" · "} Developer console

{/* Create / reveal dialog */} {createdKey ? ( <>
API key created

Copy this key now. You won't be able to see it again.

{createdKey}

Store it somewhere safe. For security, the full key is only shown once.

handleCopy(createdKey)}> {copied ? ( ) : ( )} {copied ? "Copied" : "Copy key"}
) : ( <>
Create API key

This key has full access to your organization's Supermemory data via the API.

setKeyName(e.target.value)} placeholder="e.g. production, local-dev" className={pillInputClass} autoComplete="off" onKeyDown={(e) => { if (e.key === "Enter" && !createKeyMutation.isPending) { createKeyMutation.mutate() } }} />
Expires
createKeyMutation.mutate()} disabled={createKeyMutation.isPending || !org?.id} > {createKeyMutation.isPending && ( )} {createKeyMutation.isPending ? "Creating…" : "Create"}
)}
{/* Revoke confirmation */} { if (!open && !revokeKeyMutation.isPending) setRevokeTarget(null) }} > Revoke API key? {revokeTarget?.name?.trim() ? `"${revokeTarget.name}" will stop working immediately.` : "This key will stop working immediately."}{" "} Any apps or scripts still using it will fail. This cannot be undone. Cancel { e.preventDefault() if (revokeTarget) revokeKeyMutation.mutate(revokeTarget.id) }} className="h-9 rounded-full bg-[#C73B1B] px-4 text-[13px] font-semibold text-white hover:bg-[#A83217]" > {revokeKeyMutation.isPending ? ( Revoking… ) : ( "Revoke key" )}
) }