Claves de API

La página de claves de API de un producto de IA, como las consolas de OpenAI y Anthropic. Crea claves con permisos acotados y caducidad, ve el secreto una sola vez con una copia que lo confirma, revoca con deshacer, renombra en el sitio, rota con un periodo de gracia y consulta el uso por clave.

OpenAI, Anthropic y Vercel ofrecen la misma página de claves de API: una lista de claves de las que solo ves el final, un diálogo para crear una nueva y una única oportunidad de copiar el secreto. API keys es esa página como un solo bloque que se integra en cualquier sección de Ajustes. Se ocupa de lo que suele salir mal: secretos perdidos porque se cerró un diálogo, claves revocadas por accidente y listas que saltan cuando algo desaparece.

Cada fila muestra el nombre de la clave, el secreto enmascarado como hx_live_…a3F9, sus permisos, el proyecto, cuándo se creó y cuándo se usó por última vez, o Never used. Las claves que caducan en una semana reciben una insignia de advertencia y las caducadas lo indican. Pasa usage y un pequeño gráfico de peticiones diarias aparece junto a la clave en pantallas más anchas.

Create key abre un diálogo con un nombre, un proyecto, permisos (todos, solo lectura o restringidos a los recursos que marques) y una caducidad de 30 días, 90 días, nunca o una fecha que elijas. Luego el mismo diálogo se transforma en la revelación: la clave completa en un cuadro monoespaciado, un botón Copy que confirma con una marca y una casilla que hay que marcar para que Done funcione. Si intentas cerrarlo antes de copiar, permanece abierto con una advertencia y un botón Close anyway. La pestaña también pregunta antes de cerrarse.

Revocar pregunta primero, nombrando la clave. Luego la fila se colapsa y un toast ofrece Undo durante cinco segundos, y onRevoke solo se ejecuta cuando esa ventana se cierra, así que Undo no necesita nada de tu servidor. Salir de la página o crear otra clave envía de inmediato las revocaciones pendientes. Rotate key sustituye el secreto por uno nuevo y puede mantener el antiguo funcionando una hora, un día o una semana. Renombra en el sitio con F2 o doble clic, Enter para guardar y Escape para cancelar.

  1. Añade el registro Pro a components.json

    components.json
    {
      "registries": {
        "@hextaui-pro": {
          "url": "https://hextaui.com/r/pro/{name}.json",
          "headers": {
            "Authorization": "Bearer ${HEXTAUI_PRO_TOKEN}"
          }
        }
      }
    }
  2. Añade tu token

    Crea un token en tu página de cuenta y colócalo en .env.local como HEXTAUI_PRO_TOKEN.

  3. Añade el bloque

    pnpm dlx shadcn@latest add @hextaui-pro/api-keys

Conéctalo a tu API

ApiKeySettings muestra las claves que le pasas y te llama para crearlas, revocarlas y renombrarlas. Lanza un error desde cualquier callback y la persona verá el motivo, conservando lo que escribió. Muestra ApiKeySettingsSkeleton mientras carga la lista.

"use client"

import * as React from "react"
import { IconKey } from "@tabler/icons-react"

import { SettingsSection, SettingsShell, type SettingsSectionItem } from "../settings/settings"
import {
  ApiKeySettings,
  ApiKeySettingsSkeleton,
  type ApiKey,
  type ApiKeyInput,
  type ApiKeySecret,
} from "@/components/blocks/api-keys/api-keys"

const sections: SettingsSectionItem[] = [
  { id: "api-keys", label: "API keys", icon: <IconKey /> },
]

async function request<T>(input: string, init?: RequestInit) {
  const response = await fetch(input, {
    ...init,
    headers: { "content-type": "application/json", ...init?.headers },
  })
  if (!response.ok) {
    const body = (await response.json().catch(() => ({}))) as { error?: string }
    throw new Error(body.error ?? "Something went wrong.")
  }
  return (response.status === 204 ? null : await response.json()) as T
}

export function ApiKeysPage() {
  const [keys, setKeys] = React.useState<ApiKey[] | null>(null)
  const [failed, setFailed] = React.useState(false)

  const load = React.useCallback(() => {
    setFailed(false)
    request<{ keys: ApiKey[] }>("/api/keys")
      .then((data) => setKeys(data.keys))
      .catch(() => setFailed(true))
  }, [])

  React.useEffect(load, [load])

  const create = async (input: ApiKeyInput) => {
    const result = await request<ApiKeySecret>("/api/keys", {
      method: "POST",
      body: JSON.stringify(input),
    })
    setKeys((current) => [result.key, ...(current ?? [])])
    return result
  }

  const revoke = async (key: ApiKey) => {
    await request(`/api/keys/${key.id}`, { method: "DELETE" })
    setKeys((current) => current?.filter((item) => item.id !== key.id) ?? null)
  }

  const rename = async (key: ApiKey, name: string) => {
    const updated = await request<ApiKey>(`/api/keys/${key.id}`, {
      method: "PATCH",
      body: JSON.stringify({ name }),
    })
    setKeys((current) => current?.map((item) => (item.id === key.id ? updated : item)) ?? null)
  }

  return (
    <SettingsShell sections={sections} className="h-svh">
      <SettingsSection
        id="api-keys"
        status={failed ? "error" : keys ? "ready" : "loading"}
        skeleton={<ApiKeySettingsSkeleton />}
        onRetry={load}
      >
        <ApiKeySettings
          keys={keys ?? []}
          limit={10}
          onCreate={create}
          onRevoke={revoke}
          onRename={rename}
        />
      </SettingsSection>
    </SettingsShell>
  )
}

Crea las claves en tu servidor

generateApiKey usa crypto.getRandomValues, así que funciona en Node, en runtimes edge y en Workers. Guarda un hash y los últimos cuatro caracteres, y envía el secreto de vuelta una sola vez.

import { generateApiKey, keyHint, type ApiKey, type ApiKeyInput } from "@/components/blocks/api-keys/api-keys"

async function sha256(value: string) {
  const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value))
  return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("")
}

export async function createKey(
  input: ApiKeyInput,
  save: (record: ApiKey & { hash: string }) => Promise<void>
) {
  const secret = generateApiKey("hx_live_")
  const key: ApiKey = {
    id: crypto.randomUUID(),
    name: input.name.slice(0, 60),
    hint: keyHint(secret),
    permission: input.permission,
    resources: input.resources,
    project: input.project,
    createdAt: new Date().toISOString(),
    lastUsedAt: null,
    expiresAt: input.expiresAt ? new Date(input.expiresAt).toISOString() : null,
  }
  await save({ ...key, hash: await sha256(secret) })
  return { key, secret }
}

Rotación, tus propios scopes y sin deshacer

Pasa onRotate para añadir Rotate key con un periodo de gracia para el secreto antiguo. Establece undoTimeout en 0 para revocar en cuanto se confirme, y pasa resources, prefix y snippet para adaptarlo a tu API.

"use client"

import { ApiKeySettings, type ApiKey, type ApiKeyOption, type ApiKeySecret } from "@/components/blocks/api-keys/api-keys"

const resources: ApiKeyOption[] = [
  { value: "chat", label: "Chat", description: "Send messages and read replies." },
  { value: "search", label: "Search", description: "Query your indexed documents." },
]

export function RotatingKeys({
  keys,
  api,
}: {
  keys: ApiKey[]
  api: {
    create: (input: unknown) => Promise<ApiKeySecret>
    revoke: (id: string) => Promise<void>
    rotate: (id: string, expireOldInHours: number) => Promise<ApiKeySecret>
  }
}) {
  return (
    <ApiKeySettings
      keys={keys}
      prefix="sk_test_"
      resources={resources}
      undoTimeout={0}
      expiringSoon={14}
      snippet={`curl https://api.example.com/v1/chat \\\n  -H "Authorization: Bearer $EXAMPLE_KEY"`}
      onCreate={(input) => api.create(input)}
      onRevoke={(key) => api.revoke(key.id)}
      onRotate={(key, { expireOldIn }) => api.rotate(key.id, expireOldIn)}
    />
  )
}

Anatomía

Las partes que compones, de fuera hacia dentro.

ParteDescripción
ApiKeySettingsLa lista, su encabezado con Create key, la nota del límite, el estado vacío y todos los diálogos.
ApiKeySettingsSkeletonUn marcador de carga con la forma de la lista, para la prop skeleton de SettingsSection.
generateApiKeyCrea una clave aleatoria con un prefijo a partir de crypto.getRandomValues.
keyHintLos últimos caracteres de un secreto, para guardarlos y mostrarlos como la clave enmascarada.

ApiKeySettings

También acepta todas las props de div.

PropTipoPredeterminado
keys{ id, name, hint, permission, resources?, project?, createdAt, lastUsedAt?, expiresAt?, usage? }. Las fechas admiten un Date, una cadena ISO o una marca de tiempo. usage son las peticiones por día, de la más antigua a la más reciente.
ApiKey[]–
onCreateCrea la clave y devuélvela con su secreto, mostrado una sola vez. Lanza un error para mostrar el mensaje en el diálogo.
(input: ApiKeyInput) => Promise<{ key, secret }>–
onRevokeRevoca la clave. Se ejecuta tras la ventana de deshacer. Si lanza un error, la clave vuelve con Try again.
(key) => void | Promise<void>–
onRenameAñade Rename, F2 y doble clic. El nuevo nombre se muestra al instante y se revierte si esto lanza un error.
(key, name) => void | Promise<void>–
onRotateAñade Rotate key. expireOldIn son las horas que el secreto antiguo sigue funcionando, 0 para que deje de hacerlo de inmediato.
(key, { expireOldIn }) => Promise<{ key, secret }>–
limitEl máximo de claves permitidas. Al llegar al límite, Create key se desactiva y la nota explica por qué.
number–
resourcesA qué puede tener permiso una clave restringida. Por defecto: models, responses, embeddings, files, agents y usage.
{ value, label, description? }[]–
projectsAñade un selector de Project al diálogo de creación y muestra el proyecto en cada clave.
{ value, label }[]–
prefixSe muestra antes de la pista en las claves enmascaradas, salvo que una clave tenga su propio prefijo.
string"hx_live_"
snippetEl comando mostrado en el estado vacío, con un botón Copy.
string–
undoTimeoutCuánto tiempo se ofrece Undo tras revocar, en ms. 0 revoca en cuanto se confirma.
number5000
expiringSoonDías antes de la caducidad en que una clave recibe la insignia de advertencia.
number7
nowFija la hora usada para las fechas relativas, para pruebas y capturas de pantalla.
Date–
PropTipoPredeterminado
rowsCuántas claves de marcador mostrar.
number3

generateApiKey

Devuelve el prefijo más letras y dígitos aleatorios.

PropTipoPredeterminado
prefixSe coloca al principio de la clave.
string"hx_live_"
lengthCaracteres aleatorios tras el prefijo, de 8 a 256.
number40
KeyAcción
F2Renombra la clave cuya fila tiene el foco.
EnterAl renombrar, guarda el nombre. En el diálogo de creación, crea la clave.
EscapeAl renombrar, conserva el nombre anterior. En la revelación, avisa una vez si la clave no se copió.
TabRecorre las acciones de cada clave y luego los campos y botones del diálogo.
  • Cuando se crea una clave, el foco pasa a Copy y un mensaje de estado indica que la copies ahora porque no se volverá a mostrar.
  • Las confirmaciones nombran la clave y su secreto enmascarado, y el foco empieza en Cancel.
  • Tras revocar, el foco pasa a la clave siguiente, o a la anterior, o a Create key. Tras Undo vuelve a la clave restaurada. Tras renombrar vuelve a las acciones de la clave.
  • Las insignias de restricción nombran sus recursos, el gráfico de uso se lee como un total, y los renombrados y restauraciones se anuncian.
  • Con movimiento reducido, las filas aparecen y desaparecen sin colapsarse.

Construido con

Los componentes gratuitos de HextaUI con los que está hecho API keys. Cada uno se instala por separado.

Código

4 archivos, añadidos a components/blocks/api-keys.