Api keys
ProThe account's API keys: each one's name, the part of it that's safe to show, what it can do, when it was created and last used, and when it expires. Creating a key asks for a name and its scopes (none is ticked for you), then shows the secret once with a copy button and says it won't be shown again. Revoking asks first and says what breaks: anything using the key is refused from its next request, plus what the app knows uses it.
Focus follows the work: to the name when the form opens, to the "won't be shown again" note when the key is made (so a screen reader reads the note, never the secret), and back to Create key after Cancel or "I've stored it". Keep it returns focus to the key's Revoke button; after a revoke it goes to the API keys heading, since the key's row goes. A key whose expiresAt has passed says "expired", checked against the clock as the page stays open; on the server, and until the page has hydrated, it shows only the date.
Version 5.2.0· other versions
This page is for 5.2.0, the current release. An earlier release’s page is the Markdown that release published, so the docs match the version you have installed (candor-lock.json records it).
Not in the docs of 1.3.0, 1.4.0, 1.5.0, 1.5.1, 1.6.0, 1.7.0, 1.8.0, 1.8.1, 1.9.0, 1.9.1, 2.0.0, 2.1.0, 2.2.0, 2.3.0, 2.4.0, 2.5.0, 2.5.1, 2.6.0, 2.6.1 and 2.6.2: the page came later, or had another name then.
Docs pages weren’t kept for 1.0.0, 1.0.1, 1.1.0, 1.1.1, 1.2.0 and 1.2.1: they are kept from 1.3.0 on, and before that only llms.txt was published.
Preview
Loading the preview
Install
First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/api-keys
Brings in: alert-dialog, bidi, button, checkbox, copy-field, data, heading-level, heading-scope, input, locale, locked, page-state, relative-time, tokens, utils · npm: lucide-react@^1.67.0
Usage
"use client"
import * as React from "react"
import { ApiKeys, type ApiKey } from "@/components/candor/api-keys"
const ago = (h: number) => new Date(Date.now() - h * 3_600_000).toISOString()
const scopes = [
{ id: "runs:read", label: "Read runs", description: "Runs, their logs and receipts" },
{ id: "runs:write", label: "Start runs" },
{ id: "approvals", label: "Answer approvals" },
]
export default function Example() {
const [keys, setKeys] = React.useState<ApiKey[]>([
{ id: "k1", name: "Nightly export", hint: "ck_live_…4f2a", scopes: ["runs:read"], createdAt: ago(24 * 40), createdBy: "Ana Ruiz", lastUsedAt: ago(6) },
{ id: "k2", name: "CI pipeline", hint: "ck_live_…91bc", scopes: ["runs:read", "runs:write"], createdAt: ago(24 * 3), lastUsedAt: null, expiresAt: ago(-24 * 87) },
])
return (
<ApiKeys
className="max-w-xl"
keys={keys}
scopes={scopes}
onCreate={async (name, chosen) => {
await new Promise((r) => setTimeout(r, 400))
setKeys((k) => [{ id: name, name, hint: "ck_live_…7d03", scopes: chosen, createdAt: new Date().toISOString(), lastUsedAt: null }, ...k])
return "ck_live_example_7d03"
}}
onRevoke={(id) => setKeys((k) => k.filter((x) => x.id !== id))}
revokeImpact={(k) => (k.id === "k1" ? "The nightly export job uses it." : undefined)}
/>
)
}<ApiKeys>
| Prop | Type | Default | Description |
|---|---|---|---|
| keysrequired | ApiKey[] | — | The keys, newest first as the app orders them. |
| scopesrequired | ApiKeyScope[] | — | Everything a key can be allowed to do. |
| onCreate | ((name: string, scopes: string[]) => Promise<string>) | undefined | — | Shows Create key. Called with the name and the scope ids ticked; resolve with the new secret, which is shown once. |
| onRevoke | ((id: string) => void | Promise<void>) | undefined | — | Shows Revoke on each key. Called with its id once the person confirms; the dialog waits on a returned promise. If it throws or rejects, the dialog stays open and says it failed. |
| revokeImpact | ((key: ApiKey) => string | undefined) | undefined | — | What revoking this key breaks, in the app's words ("The nightly export job uses it."), shown in the confirmation. |
| error | string | undefined | — | Why the last create failed, in the app's words, shown in the form. |
| state | ListState | undefined | — | What the list is showing: its rows (ready, the default), loading, empty or error with the reason, or partial with a note over the rows. Loading, empty and error draw in place of the rows, in this component's own words, so a list the app doesn't have yet isn't shown as an empty one. |
| onRetry | (() => void) | undefined | — | Shows Try again when `state` is an error; called when it's pressed. Pass it only when it loads the list again. |
| className | string | undefined | — | Extra classes for the outer element. |
| headingLevel | HeadingLevel | undefined | — | The level of the list's heading, so it fits under the page's own headings. Default: one below the nearest `HeadingScope`, else 2 (`<h2>`). |
Strings
The fixed words, in English by default. Change any of them under apiKeys in <LocaleProvider strings={{ … }}>; the table is exported as apiKeysStrings.
| Key | English |
|---|---|
| label | API keys |
| heading | API keys |
| create | Create key |
| empty | No keys yet. |
| loading | Loading API keys |
| error | API keys couldn’t be loaded. |
| name | Name |
| namePlaceholder | Nightly export |
| scopes | What it can do |
| createIt | Create |
| creating | Creating… |
| lockedCreating | Creating the key |
| cancel | Cancel |
| needsName | Name the key first. |
| needsScope | Choose at least one thing it can do. |
| shownOnce | Copy this key now and keep it somewhere safe. It won’t be shown again. |
| secret | New key: {name} |
| stored | I’ve stored it |
| created | created {when} |
| createdBy | created {when} by {name} |
| lastUsed | last used {when} |
| neverUsed | never used |
| notRecorded | last use not recorded |
| expires | expires {date} |
| expired | expired {date} |
| expiry | expiry {date} |
| revoke | Revoke |
| revokeKey | Revoke {name} |
| revokeTitle | Revoke {name}? |
| revokeBreaks | Anything using {name} ({hint}) is refused from its next request. |
| revokeIrreversible | A revoked key can’t be restored. Create a new key and update whatever used this one. |
| keep | Keep it |
| createFailed | Creating the key failed. |
| revokeFailed | Revoking failed. Try again, or keep the key. |
| keepLocked | Revoking is under way, so the key can’t be kept now. If it fails, you can keep it. |
Types
- ApiKey
- One API key as the app lists it. The secret itself is never here: it's shown once, when the key is created.
{ id: string; name: string; /** The part that's safe to show again, e.g. "ck_live_…4f2a". */ hint: string; /** Scope ids from `scopes`. */ scopes: string[]; createdAt: string; /** Who created it, by name. */ createdBy?: string | undefined; /** When it was last used; `null` if never. Leave it out when the app doesn't record use, and the row says so. */ lastUsedAt?: string | null | undefined; /** When it stops working on its own. Leave it out for a key that doesn't expire. */ expiresAt?: string | undefined; }- ApiKeyScope
- Something a key can be allowed to do.
{ id: string; /** What it allows, e.g. "Read runs". */ label: string; /** A second line, e.g. "Runs, their logs and receipts". */ description?: string | undefined; }
Types from other items
The props above take these. Each is defined by another item; import it from the path shown.
- ListState from @/lib/candor/data
- What a list or table is showing, the same shape as `ChartState` and as page-state's `ListState` (which the free tier ships, so it declares its own rather than import this file): its rows (ready), the shape of rows still coming (loading), nothing and why (empty), the rows it has with a note on what's missing (partial), or why it couldn't load (error). Lists without a `state` show their rows.
ChartState- HeadingLevel from @/lib/candor/heading-level
- A heading's level in the page's outline: `1` is `<h1>`, `6` is `<h6>`. Every Candor item that draws a heading takes its `headingLevel` as this type; the level is where the heading sits in the outline, never how large it looks.
1 | 2 | 3 | 4 | 5 | 6- ChartState from @/lib/candor/data
{ kind: "ready"; } | { kind: "loading"; } /** Nothing to plot, and why. */ | { kind: "empty"; reason: string; } /** Some of the range is missing. */ | { kind: "partial"; note: string; } | { kind: "error"; reason: string; }