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

# Credentials

> Three credentials, what each one describes, and where each is accepted.

Peeve has three credentials. They are not interchangeable, and no endpoint
accepts a fallback — an endpoint declares which kinds it takes, and anything else
is refused with a message naming what was wanted.

|                          | Describes                       | Used by                                    | Permissions come from                                             |
| ------------------------ | ------------------------------- | ------------------------------------------ | ----------------------------------------------------------------- |
| **Publishable `pk_…`**   | the **workspace**, to a browser | the widget, agent artifacts                | none — the [origin check](/authentication/origins) is the control |
| **Secret `sk_…`**        | a **machine integration**       | the SDK's server half, the CLI, the wizard | the key's own stored role                                         |
| **User token `pv_ut_…`** | a **person**                    | the `/v1` API and MCP                      | that teammate's **live** workspace role                           |

## The user token is the one to understand

<Warning>
  **A user token's role is resolved live, on every call.** Nothing is frozen onto
  the credential.

  Demote a teammate and their token narrows immediately. Remove them from the
  workspace and it stops working entirely — the membership read *is* the
  existence check, so there is no window in which a removed person's token still
  works.

  This is the model's main advantage, and it changes how you manage access:
  revoking someone's reach is a membership change, not a credential hunt.
</Warning>

A role frozen onto a credential is a copy of an authorization decision, and
copies go stale. That is why user tokens are also **not cached**, where key
lookups are cached for 20 seconds — caching a live role would reintroduce
exactly the staleness the design exists to remove.

### One person, many tokens

A teammate can hold several named tokens — one per script, one per laptop — and
revoke them individually. Create, name, list and revoke them in
**Settings → API keys**.

They all carry the same role, because they all carry *that person's* role.

<Warning>
  **A user token is not scoped to one surface.** The same `pv_ut_…` that connects
  an assistant to [MCP](/mcp/overview) also reaches the whole
  [`/v1` API](/api/overview) at that person's role — including, for an owner or
  admin, writing guardrails and operating the kill switch.

  Treat handing one out as handing out that person's account reach, not "an MCP
  key". If you want a narrow credential, issue it to a teammate whose role is
  narrow.
</Warning>

## Which credential each surface takes

| Surface                                       | Credential                                       |
| --------------------------------------------- | ------------------------------------------------ |
| `/v1` data and configuration (14 endpoints)   | **User token**                                   |
| `POST /v1/users`                              | **Secret key**                                   |
| `POST /v1/answer`                             | **Secret key**                                   |
| `GET`/`POST /api/mcp/{workspace}`             | **User token**                                   |
| `GET`/`POST /api/mcp/{workspace}/u/{contact}` | A `pv_grant_…`, **or** a user token with `reply` |
| The widget                                    | **Publishable key**                              |
| Agent artifacts                               | **Publishable key**, in the URL path             |

`/v1` is deliberately **not** uniform. The data and config endpoints are a person
acting with their own reach, so only a user token can express them. `/v1/users`
and `/v1/answer` are machine paths that run in your backend on a schedule or a
signup hook — a user token there would tie a production pipeline to one
employee's continued employment.

<Note>
  **Publishable keys are accepted nowhere on `/v1` or MCP.** A `pk_…` ships inside
  your page's script tag, so every script on that page can read it — including a
  prompt-injection payload. A publishable key that could read `/v1/leads` would
  put your whole pipeline one XSS away.
</Note>

## Sending a credential

Header only, on both `/v1` and MCP:

```bash theme={null}
-H "Authorization: Bearer pv_ut_xxx"
-H "X-Peeve-Key: pv_ut_xxx"
```

<Warning>
  **Never in a query string.** URLs end up in proxy logs, browser history and
  `Referer` headers; a credential in one is a credential leaked to every hop.
  `/v1` and MCP read credentials from headers only, by design.
</Warning>

The widget is the exception — its publishable key belongs in the page, because
it is public by construction:

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

<Warning>
  **Never put a secret key or a user token in browser code.** This is the
  highest-stakes rule on this page. Client-side, publishable only.
</Warning>

## Who can see which credential

| Credential                | Visible to                   |
| ------------------------- | ---------------------------- |
| Publishable + secret keys | **Owner and Developer only** |
| Your own user tokens      | You                          |

Admin, Responder and Viewer **cannot** see the workspace keys — deliberately, and
it surprises people.

The reasoning: the publishable and secret keys are the workspace's *integration*
credentials — the widget, the CLI, the SDK. Whoever reads the secret key holds
whatever it holds, so that list is the people whose job is wiring Peeve up: the
Owner, and the Developer whose whole remit is the install. An Admin's reach is
over how the AI behaves — guardrails, drift, the team — which is a different
axis, served by their own user token.

## No CORS, no origin

`/v1` sends **no CORS headers** and checks **no `Origin`**. Both are deliberate:

* No CORS means the API is not callable from a page by construction, so a
  credential can never be exercised from a victim's browser.
* No origin check because a backend sends no `Origin` header — applying the
  browser gate here would reject every legitimate call.

The origin gate belongs to the widget surface, and only there.

## What survives the kill switch

The [kill switch](/authentication/keys#the-kill-switch) is **not** enforced on
`/v1`. It stops the agent acting on your app; it must not stop you reading your
own data — and it must certainly not lock you out of
[the endpoint that turns it back off](/api/v1/configuration/set-the-kill-switch).

## Next

<CardGroup cols={2}>
  <Card title="Roles" icon="users" href="/authentication/roles">
    Which role reaches which endpoint.
  </Card>

  <Card title="API conventions" icon="brackets-curly" href="/api/conventions">
    The envelope, pagination and error shape shared by every `/v1` endpoint.
  </Card>
</CardGroup>
