> ## 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.

# Authentication

> Two kinds of key, and the boundary each one defends.

Peeve has two kinds of API key. Confusing them is the most damaging mistake you
can make with this API, so this page is worth reading in full before you write
any integration code.

## The two keys

<CardGroup cols={2}>
  <Card title="Publishable — pk_…" icon="globe">
    **Public by construction.** It ships in your page source, where any script
    on the page can read it. That is expected. What controls access is the
    request origin, not secrecy.
  </Card>

  <Card title="Secret — sk_…" icon="lock">
    **A bearer credential with full workspace authority.** Server-to-server
    only. Never in a browser, never in client-side code, never in a repository.
  </Card>
</CardGroup>

## Which key reaches what

| Endpoint                            | Publishable               | Secret       |
| ----------------------------------- | ------------------------- | ------------ |
| `POST /v1/users`                    | Rejected                  | **Required** |
| `GET`/`POST /api/killswitch`        | Rejected                  | **Required** |
| The widget's own calls              | Required                  | Rejected     |
| `GET /api/agent/{pk}/{artifact}`    | Required, in the URL path | Rejected     |
| `GET`/`POST /api/mcp/{workspaceId}` | Not used                  | Not used     |

The gate is exact in both directions. `/v1/users` resolves your key with a
publishable key explicitly excluded, so a `pk_…` lifted from a page cannot reach
it — and it emits no CORS headers at all, so it is not callable from a page by
construction. Equally, the widget's endpoints resolve with a secret key
excluded, so a secret key sent there returns `401`.

<Warning>
  There is no endpoint where either key works. If you are unsure which one an
  endpoint needs, the table above is the answer, and the API reference states it
  per operation.
</Warning>

## How to send a key

**Secret key** — as a bearer token, or in the Peeve header:

```bash theme={null}
curl https://api.peeve.ai/v1/users \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{"external_user_id": "user_8412"}'
```

```bash theme={null}
curl https://api.peeve.ai/v1/users \
  -H "X-Peeve-Key: sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{"external_user_id": "user_8412"}'
```

**Publishable key** — you do not normally send this yourself. It goes in your
widget script tag, and the widget handles the rest:

```html theme={null}
<script
  src="https://cdn.peeve.ai/widget.js"
  data-publishable-key="pk_live_xxx"
  defer
></script>
```

It also appears as a path segment on the
[agent artifact URLs](/artifacts/overview), which is safe because the key is
public by construction:

```bash theme={null}
curl https://api.peeve.ai/api/agent/pk_live_xxx/llms.txt
```

<Note>
  Sending an `Origin` header matters. From a browser it is set for you. From
  cURL or a server you must set it yourself, or the origin check will refuse the
  request in production.
</Note>

## What a key resolves to

A key resolves to exactly one workspace, and that is the tenant boundary. The
workspace id comes from the validated key — never from anything in the request
body — so nothing a caller sends can reach another workspace's data.

Key lookups are cached briefly (20 seconds). This is what bounds how long a
revoked key or a flipped kill switch takes to take effect: within 30 seconds,
and immediately for the kill switch, which clears the cache when it flips.

## Next

<CardGroup cols={2}>
  <Card title="Creating and rotating keys" icon="key" href="/authentication/keys">
    Where keys come from, how they are stored, and how to rotate one safely.
  </Card>

  <Card title="Origins" icon="shield" href="/authentication/origins">
    The exact rule that makes a public key safe to publish.
  </Card>
</CardGroup>
