Skip to content
Tier

Model key

Pro

The person's own key for a model provider (bring your own key): empty, it says what a key unlocks and what works without one; adding one asks for the provider (none is chosen for you when there are several) and the key, says where data goes once it's added, right beside Add key, and checks it with the provider before it's stored. While it checks, Add key says so and stays locked, and so does Cancel when replacing, saying why; a key the provider refuses says what to check. Once the provider accepts a key it says "Verified with …, saving…" until the app's stored changes, never "No key added" or the old key in between. Verified, it shows the provider, the part of the key that's safe to show, who added it and when, and Replace and Remove; the key itself is never shown again. Remove asks first, in place, and says what stops working and that the key isn't kept.

Focus follows the work: to the key field when the form opens, to the verified key's heading once a key is added, to Replace after a cancelled replace, to Remove after Keep it, and to the heading after a removal; a check or a removal that finishes after the person has moved focus out of the item leaves focus where it is and is announced. With readOnly, Add key, Replace and Remove stay in place, locked, saying why.

Markdown
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, 2.6.2, 2.7.0, 2.8.0, 2.9.0, 2.10.0, 3.0.0, 4.0.0, 4.0.1, 4.0.2, 4.1.0, 4.2.0 and 4.3.0: 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

With the Candor CLI

First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/model-key

Brings in: bidi, button, heading-level, heading-scope, input, locale, locked, radio-group, relative-time, tokens, use-announcer, use-focus-within, utils · npm: lucide-react@^1.67.0

Usage

TSX · The example above
"use client"

import * as React from "react"
import { ModelKey, type StoredModelKey } from "@/components/candor/model-key"

export default function Example() {
  // A real app checks the key with the provider on its server and stores only the key's record; this one accepts any
  // key that starts with "pa-" and keeps its last four characters as the part that's safe to show.
  const [stored, setStored] = React.useState<StoredModelKey | null>(null)
  return (
    <ModelKey
      className="max-w-lg"
      providers={[
        { id: "a", name: "Provider A", placeholder: "pa-…" },
        { id: "b", name: "Provider B", placeholder: "pb-…" },
      ]}
      stored={stored}
      unlocks={["Chat and agents on your own model and account", "Usage billed by the provider, not by us"]}
      without={["Audits, reports and monitors", "Everything you’ve already set up"]}
      onAdd={async (provider, key) => {
        await new Promise((r) => setTimeout(r, 600))
        if (!key.startsWith(`p${provider}-`)) return { ok: false, reason: "The provider answered: invalid key." }
        setStored({ provider, hint: `p${provider}-…${key.slice(-4)}`, addedAt: new Date().toISOString(), addedBy: "Dana Ortiz" })
        return { ok: true }
      }}
      onRemove={async () => {
        await new Promise((r) => setTimeout(r, 400))
        setStored(null)
      }}
    />
  )
}

<ModelKey>

PropTypeDefaultDescription
providersrequiredModelKeyProvider[]—The providers whose keys the app takes. With one, it's named and not asked for.
storedStoredModelKey | null | undefined—The stored key; left out (or `null`) while there is none.
unlocksstring[] | undefined[]What a key unlocks, in the app's words, shown while there is none.
withoutstring[] | undefined[]What works without a key, in the app's words, shown while there is none.
onAdd((provider: string, key: string) => Promise<ModelKeyCheck>) | undefined—Shows Add key (and Replace). Called with the provider's id and the key; check it with the provider and store it, then resolve with what the check found, and update `stored` (until it changes, the key reads as saving). If it throws or rejects, the form says the key couldn't be checked and that nothing was stored.
onRemove(() => void | Promise<void>) | undefined—Shows Remove. Called once the person confirms; the confirmation waits on a returned promise and says if it fails.
readOnlystring | undefined—Why this person can't add, replace or remove the key, e.g. "Only admins manage the model key."
headingLevelHeadingLevel | undefined—The level of the heading. Defaults to one below the nearest `HeadingScope`, else 2 (it was 3 before 5.0); the item's own headings sit one below it.
classNamestring | undefined—Classes, merged with Candor's.

Strings

The fixed words, in English by default. Change any of them under modelKey in <LocaleProvider strings={{ … }}>; the table is exported as modelKeyStrings.

KeyEnglish
headingYour model key
noneNo key added. Add one from your own account with a model provider.
unlocksA key unlocks
withoutWithout one
providerProvider
keyKey
keyFor{name} key
keyHintIt’s checked with {provider}, then stored. It’s never shown again, here or anywhere.
keyHintNoProviderIt’s checked with the provider, then stored. It’s never shown again, here or anywhere.
egressWith this key added, chat and your agents can send data to {provider}.
addAdd key
verifyingChecking with {provider}…
cancelCancel
needsProviderChoose a provider first.
needsKeyPaste the key first.
waitVerifyWait for the check to finish.
cancelLockedThe key is being checked, so the replace can’t be cancelled now. Once the check finishes, you can cancel.
invalid{provider} didn’t accept this key
invalidWhyThe check failed without saying why.
checkWhat to check
checkWholeThe key is copied whole, with nothing added at either end.
checkProviderIt’s a key for {provider}, not for another provider.
checkActiveIt’s still active in your {provider} account.
verifyFailedCouldn’t check the key. Nothing was stored; try again.
verifiedVerified with {provider}
savingVerified with {provider}, saving…
addedAdded {when}
addedByAdded by {name} {when}
neverShownThe key itself is never shown again. To change it, replace it.
refused{provider} now refuses this key
replaceReplace
removeRemove
removeTitleRemove the {provider} key?
removeBreaksChat and agents that use it stop working with {provider} until a key is added again.
removeIrreversibleThe key isn’t kept anywhere once it’s removed: to use it again, paste it again.
keepKeep it
removeItRemove key
removingRemoving…
waitRemoveWait for the key to be removed.
keepLockedRemoving is under way, so the key can’t be kept now. If it fails, you can keep it.
removeFailedRemoving failed. Try again, or keep the key.
removedKey removed

Types

ModelKeyProvider
A model provider whose key the person can bring, as the app lists it.
{ id: string; /** Its name, e.g. "Anthropic". */ name: string; /** What its keys start with, shown as the field's placeholder, e.g. "sk-ant-…". */ placeholder?: string | undefined; /** Where data goes once this provider's key is added, in the app's words; replaces the default line. */ egress?: string | undefined; /** What to check when this provider refuses a key, in the app's words; replaces the default list. */ check?: string[] | undefined; }
StoredModelKey
The stored key, as the app's server describes it. The key itself is never here: only the part safe to show.
{ /** The provider's id, from `providers`. */ provider: string; /** The part of the key that's safe to show, e.g. "sk-ant-…4f2a". */ hint: string; /** When it was added, as an ISO time. */ addedAt: string; /** Who added it, by name. Left out, no name is shown. */ addedBy?: string | undefined; /** The provider refuses it now (revoked or expired there), in the app's or the provider's words. */ problem?: string | undefined; }
ModelKeyCheck
What checking a key found, as the app's server reports it. On `ok`, update `stored` with the key's record.
{ ok: true } | { ok: false; reason?: string | undefined }

Types from other items

The props above take these. Each is defined by another item; import it from the path shown.

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