> ## Documentation Index
> Fetch the complete documentation index at: https://docs.peeve.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Creating and rotating keys

> Where keys come from, how they are stored, and how to roll one without downtime.

## Creating a key

Keys are created in the dashboard under **Settings → API keys**. Choose the
kind — publishable or secret — and Peeve generates it.

<Warning>
  A key is shown **once**, at creation. Copy it then.
</Warning>

## How keys are stored

The two kinds are stored differently, and the reason is the same reason they
are named differently.

**Secret keys are stored hashed.** Peeve keeps a hash, not the key, so a
database dump does not yield working credentials — and neither Peeve nor you can
recover a lost secret key. The only remedy for a lost secret key is to rotate
it.

**Publishable keys are stored in the clear**, because they are public by
construction. The key already ships in your page source where anyone can read
it; storing it hashed would protect nothing and would stop the dashboard from
showing you your own install snippet.

Every key lookup also stamps a `last_used_at` timestamp. That is what the
dashboard's install state reads to tell you the snippet is live.

## Rotating a key

Rotation is create-then-revoke, in that order. Both keys work in the overlap,
so there is no downtime.

<Steps>
  <Step title="Create the replacement">
    Create a new key of the same kind in **Settings → API keys**. Copy it.
  </Step>

  <Step title="Deploy it">
    Update your environment (for a secret key) or your script tag and any
    `Peeve.init` call (for a publishable key). Deploy, and confirm traffic is
    flowing on the new key.
  </Step>

  <Step title="Revoke the old one">
    Revoke the previous key. A revoked key stops resolving, so every request
    using it starts failing.

    Allow up to 20 seconds for the change to propagate — key lookups are cached
    for that long.
  </Step>
</Steps>

<Note>
  Rotating a key never affects your users' identities. Contacts are keyed on
  `(workspace, external_user_id)`, deliberately not on the key — if identity
  were tied to the credential, every user would be orphaned the day you rotated
  and their history would disappear.
</Note>

## If a secret key leaks

Revoke it immediately, then create a replacement. A secret key is possession-based:
whoever holds it can push users and flip your kill switch.

<Warning>
  A leaked secret key is not contained by revoking it later — assume everything
  it could reach was reached. Revoke first, investigate second.
</Warning>

## Storing keys

* **Secret keys** belong in your server environment. Never in client-side code,
  never in a bundled file, never committed. If a secret key ever appears in a
  browser network tab, it has leaked.
* **Publishable keys** belong in your page. Putting one in an environment
  variable prefixed for client exposure (`NEXT_PUBLIC_…`, `VITE_…`) is correct
  and expected.

```js theme={null}
// Correct — a publishable key is meant to be here.
Peeve.init({ publishableKey: process.env.NEXT_PUBLIC_PEEVE_PUBLISHABLE_KEY });
```

```js theme={null}
// Wrong. This ships your secret key to every visitor.
Peeve.init({ publishableKey: process.env.PEEVE_SECRET_KEY });
```

The second example does not merely fail — the widget's endpoints reject secret
keys, so the widget would stop working *and* the key would be exposed.

## The kill switch

Independent of key revocation, every workspace has a kill switch that stops
Peeve serving entirely: the widget does not mount, the agent returns an empty
completed plan, and the MCP and artifact endpoints stop responding.

It is controlled with a secret key:

```bash theme={null}
# Activate
curl -X POST https://api.peeve.ai/api/killswitch \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{"on": true}'

# Read current state
curl https://api.peeve.ai/api/killswitch \
  -H "Authorization: Bearer sk_live_xxx"
```

A POST with no body activates it. Flipping it clears the key cache, so it takes
effect on the very next request rather than after the 20-second TTL.

See [the API reference](/api/server/kill-switch/toggle-the-kill-switch) for the full contract.
