Chaves de API

A página de chaves de API de um produto de IA, como nos consoles da OpenAI e da Anthropic. Crie chaves com permissões restritas e uma expiração, veja o segredo uma única vez com uma cópia que confirma, revogue com desfazer, renomeie no próprio lugar, faça a rotação com um período de tolerância e veja o uso por chave.

OpenAI, Anthropic e Vercel têm todas a mesma página de chaves de API: uma lista de chaves das quais você só vê o final, um diálogo para criar uma nova e uma única chance de copiar o segredo. O API keys é essa página como um único bloco que se encaixa em qualquer seção de Configurações. Ele cuida das partes que dão errado: segredos perdidos porque um diálogo foi fechado, chaves revogadas por engano e listas que pulam quando algo sai.

Cada linha mostra o nome da chave, o segredo mascarado como hx_live_…a3F9, suas permissões, o projeto, quando foi criada e quando foi usada pela última vez, ou Never used. Chaves que expiram em até uma semana recebem um badge de aviso, e as expiradas informam isso. Passe usage e um pequeno gráfico de requisições diárias aparece ao lado da chave em telas mais largas.

Create key abre um diálogo com nome, projeto, permissões (todas, somente leitura ou restritas aos recursos que você marcar) e uma expiração de 30 dias, 90 dias, nunca ou uma data à sua escolha. Em seguida, o mesmo diálogo se transforma na revelação: a chave completa em uma caixa monoespaçada, um botão Copy que confirma com um check e uma caixa para marcar antes de Done funcionar. Se tentar fechá-lo antes de copiar, ele permanece aberto com um aviso e um botão Close anyway. A aba também pergunta antes de fechar.

Revogar pergunta antes, citando a chave. Depois a linha se recolhe e um toast oferece Undo por cinco segundos, e onRevoke só roda quando essa janela se fecha, então o Undo não exige nada do seu servidor. Sair da página ou criar outra chave envia imediatamente quaisquer revogações em espera. Rotate key troca por um novo segredo e pode manter o antigo funcionando por uma hora, um dia ou uma semana. Renomeie no próprio lugar com F2 ou duplo clique, Enter para salvar e Escape para cancelar.

  1. Adicione o registro Pro ao components.json

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

    Crie um token na sua página de conta e coloque-o em .env.local como HEXTAUI_PRO_TOKEN.

  3. Adicione o bloco

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

Conecte à sua API

ApiKeySettings mostra as chaves que você passa e chama você para criá-las, revogá-las e renomeá-las. Lance um erro em qualquer callback e a pessoa vê o motivo, com o que digitou preservado. Mostre ApiKeySettingsSkeleton enquanto a lista carrega.

"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>
  )
}

Crie chaves no seu servidor

generateApiKey usa crypto.getRandomValues, então funciona no Node, em runtimes edge e em Workers. Armazene um hash e os quatro últimos caracteres, e envie o segredo de volta uma única 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 }
}

Rotação, escopos próprios e sem desfazer

Passe onRotate para adicionar Rotate key com um período de tolerância para o segredo antigo. Defina undoTimeout como 0 para revogar no momento em que for confirmado e passe resources, prefix e snippet para combinar com a sua 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)}
    />
  )
}

Anatomia

As partes que você compõe, de fora para dentro.

ParteDescrição
ApiKeySettingsA lista, seu cabeçalho com Create key, a nota de limite, o estado vazio e todos os diálogos.
ApiKeySettingsSkeletonUm placeholder de carregamento com o formato da lista, para a prop skeleton do SettingsSection.
generateApiKeyCria uma chave aleatória com um prefixo a partir de crypto.getRandomValues.
keyHintOs últimos caracteres de um segredo, para armazenar e exibir como a chave mascarada.

ApiKeySettings

Também aceita todas as props de div.

PropTipoPadrão
keys{ id, name, hint, permission, resources?, project?, createdAt, lastUsedAt?, expiresAt?, usage? }. As datas aceitam um Date, uma string ISO ou um timestamp. usage são as requisições por dia, da mais antiga para a mais recente.
ApiKey[]–
onCreateCrie a chave e devolva-a com seu segredo, exibido uma única vez. Lance um erro para mostrar a mensagem no diálogo.
(input: ApiKeyInput) => Promise<{ key, secret }>–
onRevokeRevoga a chave. Executa após a janela de desfazer. Se lançar um erro, a chave volta com Try again.
(key) => void | Promise<void>–
onRenameAdiciona Rename, F2 e duplo clique. O novo nome aparece de imediato e volta ao anterior se isto lançar um erro.
(key, name) => void | Promise<void>–
onRotateAdiciona Rotate key. expireOldIn é quantas horas o segredo antigo continua funcionando, 0 para encerrar de imediato.
(key, { expireOldIn }) => Promise<{ key, secret }>–
limitO máximo de chaves permitido. No limite, Create key é desativado e a nota explica o motivo.
number–
resourcesO que uma chave restrita pode ter permissão para usar. O padrão é models, responses, embeddings, files, agents e usage.
{ value, label, description? }[]–
projectsAdiciona um seletor de Project ao diálogo de criação e mostra o projeto em cada chave.
{ value, label }[]–
prefixExibido antes da dica nas chaves mascaradas, a menos que uma chave tenha seu próprio prefixo.
string"hx_live_"
snippetO comando exibido no estado vazio, com um botão Copy.
string–
undoTimeoutPor quanto tempo o Undo é oferecido após revogar, em ms. 0 revoga assim que é confirmado.
number5000
expiringSoonDias antes da expiração em que uma chave recebe o badge de aviso.
number7
nowFixa o horário usado nas datas relativas, para testes e screenshots.
Date–
PropTipoPadrão
rowsQuantas chaves de placeholder mostrar.
number3

generateApiKey

Retorna o prefixo mais letras e dígitos aleatórios.

PropTipoPadrão
prefixColocado no início da chave.
string"hx_live_"
lengthCaracteres aleatórios após o prefixo, de 8 a 256.
number40
TeclaAção
F2Renomeia a chave cuja linha tem o foco.
EnterAo renomear, salva o nome. No diálogo de criação, cria a chave.
EscapeAo renomear, mantém o nome antigo. Na revelação, avisa uma vez se a chave não foi copiada.
TabPercorre as ações de cada chave, depois os campos e botões do diálogo.
  • Quando uma chave é criada, o foco vai para Copy e uma mensagem de status diz para copiá-la agora, pois ela não será exibida de novo.
  • As confirmações citam a chave e seu segredo mascarado, e o foco começa em Cancel.
  • Após revogar, o foco vai para a próxima chave, ou a anterior, ou Create key. Após Undo, volta para a chave restaurada. Após renomear, volta para as ações da chave.
  • Os badges de restrito citam seus recursos, o gráfico de uso é lido como um total e as renomeações e restaurações são anunciadas.
  • Com movimento reduzido, as linhas aparecem e saem sem se recolher.

Construído com

Os componentes gratuitos do HextaUI de que API keys é feito. Cada um é instalado separadamente.

Código

4 arquivos, adicionados a components/blocks/api-keys.