Data display
API keysPro
API keys for a workspace: create with scopes and an expiry, see the secret once, rotate and revoke with confirmation, and see when and where each key was last used. Every change goes through your callbacks.
npx prodular-ui add api-keysPart of Prodular UI Pro. Preview and read the docs freely, install with a license. See pricing
Developer settings
Create a key to see the one-time reveal. One key expires in 6 days and one has expired. Revoke asks you to type the key name.
API keys
3 keys
- NetSuite sync
rl_live_8f2a••••••••Read invoicesRecord payments+1Used 2 hours agofrom 34.120.18.6Expires Sep 3, 2027Expires Sep 3, 2027 - Data warehouse export
rl_live_c71d••••••••Read invoicesRead paymentsUsed 2 days agofrom SnowflakeExpires Oct 28, 2026Expires in 6 days - Old Zapier key
rl_live_0b44••••••••Manage webhooksNever usedCreated Apr 12, 2026Expired Oct 12, 2026Expired Oct 12, 2026
"use client"
import * as React from "react"
import { ApiKeysManager, type ApiKey, type ApiScope } from "@/components/ui/api-keys"
const now = new Date(Date.UTC(2026, 9, 22, 16, 30))
const scopes: ApiScope[] = [ { id: "invoices:read", label: "Read invoices", group: "Invoices" }, { id: "invoices:write", label: "Create and edit invoices", group: "Invoices" }, { id: "payments:read", label: "Read payments", group: "Payments" }, { id: "payments:write", label: "Record payments", description: "Mark invoices paid from your bank feed.", group: "Payments" }, { id: "customers:read", label: "Read customers", group: "Customers" }, { id: "webhooks:write", label: "Manage webhooks", group: "Developers" },]
const initial: ApiKey[] = [ { id: "key_1", name: "NetSuite sync", prefix: "rl_live_8f2a", scopes: ["invoices:read", "payments:write", "customers:read"], createdAt: new Date(Date.UTC(2026, 8, 3)), createdBy: "Maya Okafor", expiresAt: new Date(Date.UTC(2027, 8, 3)), lastUsedAt: new Date(Date.UTC(2026, 9, 22, 14, 2)), lastUsedFrom: "34.120.18.6", }, { id: "key_2", name: "Data warehouse export", prefix: "rl_live_c71d", scopes: ["invoices:read", "payments:read"], createdAt: new Date(Date.UTC(2026, 6, 28)), createdBy: "Theo Lindqvist", expiresAt: new Date(Date.UTC(2026, 9, 28)), lastUsedAt: new Date(Date.UTC(2026, 9, 21, 2, 0)), lastUsedFrom: "Snowflake", }, { id: "key_3", name: "Old Zapier key", prefix: "rl_live_0b44", scopes: ["webhooks:write"], createdAt: new Date(Date.UTC(2026, 3, 12)), createdBy: "Priya Raman", expiresAt: new Date(Date.UTC(2026, 9, 12)), lastUsedAt: null, },]
const randomSecret = () => { const bytes = new Uint8Array(24) crypto.getRandomValues(bytes) return "rl_live_" + Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("")}
export default function ApiKeysDeveloper() { const [keys, setKeys] = React.useState(initial) return ( <ApiKeysManager keys={keys} scopes={scopes} defaultScopes={["invoices:read"]} now={now} timeZone="UTC" className="max-w-4xl" onCreate={async (input) => { await new Promise((r) => setTimeout(r, 500)) const secret = randomSecret() const key: ApiKey = { id: `key_${Date.now()}`, name: input.name, prefix: secret.slice(0, 12), scopes: input.scopes, createdAt: now, createdBy: "Maya Okafor", expiresAt: input.expiresInDays === null ? null : new Date(now.getTime() + input.expiresInDays * 864e5), lastUsedAt: null, } setKeys((k) => [key, ...k]) return { key, secret } }} onRotate={async (old) => { await new Promise((r) => setTimeout(r, 500)) const secret = randomSecret() const key = { ...old, prefix: secret.slice(0, 12), createdAt: now, lastUsedAt: null } setKeys((k) => k.map((x) => (x.id === old.id ? key : x))) return { key, secret } }} onRevoke={async (key) => { await new Promise((r) => setTimeout(r, 500)) setKeys((k) => k.filter((x) => x.id !== key.id)) }} /> )}Anatomy
<ApiKeysManager keys={keys} scopes={scopes} onCreate={createKey} onRotate={rotateKey} onRevoke={revokeKey}/>Guidance
Use it when
- Developer settings where customers create keys for their own integrations.
- One key per service, with only the scopes it needs, so one can be rotated without touching the rest.
- Expiry by default; never only when people choose it on purpose.
Avoid
- Showing or storing the full secret after creation. The list only ever shows the prefix.
- Personal access tokens with sign in semantics. Those belong in the account security page.
Rules for AI agents
Published in llms-full.txt and the registry, so coding agents follow them when they build with this component.
- Store only a hash of the secret and its prefix on the server. onCreate and onRotate return { key, secret } once; never return the secret again.
- Pass keys as { id, name, prefix, scopes, createdAt, createdBy?, expiresAt?, lastUsedAt?, lastUsedFrom? } and scopes as { id, label, description?, group? }.
- Throw from onCreate, onRotate or onRevoke to show the error in the dialog; the dialog stays open.
- Rotate replaces the secret at once. If you support a grace period, say so in your own docs and keep the old hash valid server side.
- Pass now and timeZone in tests and demos so expiry warnings and relative times are stable.
API
ApiKeysManager
| Prop | Type | Default | Description |
|---|---|---|---|
| keys / scopes | ApiKey[] / ApiScope[] | Existing keys and the scopes people can grant. | |
| onCreate | (input: { name, scopes, expiresInDays }) => Promise<{ key, secret }> | Creates a key. The secret shows once. | |
| onRotate | (key) => Promise<{ key, secret }> | Replaces a key's secret. | |
| onRevoke | (key) => Promise<void> | Revokes after typing the key name. | |
| expiryOptions | (number | null)[] | [30, 90, 365, null] | Days a new key lasts; null never expires. |
| defaultScopes | string[] | [] | Ticked when the create dialog opens. |
| now / timeZone | Date / string | Clock and zone for dates and warnings. |