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

# Roles

> The role matrix, and which role each endpoint requires.

A [user token](/authentication/credentials) carries its owner's workspace role,
read live on every call. This page is what that role reaches.

## The matrix

Roles are cut by **how much they widen Peeve's reach**, not by seniority.

| Capability                             | Owner | Admin | Responder | Developer | Viewer |
| -------------------------------------- | :---: | :---: | :-------: | :-------: | :----: |
| Read workspace data                    |   ✅   |   ✅   |     ✅     |     ✅     |    ✅   |
| Reply to and update hand-offs          |   ✅   |   ✅   |     ✅     |     —     |    —   |
| Approve drift changes                  |   ✅   |   ✅   |     —     |     —     |    —   |
| Grant capabilities, write contact data |   ✅   |   ✅   |     —     |     —     |    —   |
| Operate the kill switch                |   ✅   |   ✅   |   **✅**   |     —     |    —   |
| Change workspace settings              |   ✅   |   ✅   |     —     |     —     |    —   |
| Billing and plan                       |   ✅   |   —   |     —     |     —     |    —   |

Two details that catch people out:

<Note>
  **Responder holds the kill switch.** The safe direction must be available to
  whoever is on shift, not only to an owner who may be asleep.
</Note>

<Note>
  **Developer holds none of the gated capabilities.** It is a reach-restricted,
  technical-setup role: the install, the snippet, the CLI, widget appearance,
  connectors, and viewing the dashboard — none of which are gated by this matrix.
  It widens Peeve's reach the least of any role that can still do setup.

  A Developer *can* see the workspace publishable and secret keys, which an Admin
  cannot. That is a different axis — see
  [Credentials](/authentication/credentials#who-can-see-which-credential).
</Note>

Every role can read. That mirrors the dashboard, where every role can view the
console.

## Which role each endpoint needs

The API maps onto the capability the **dashboard** already requires for the same
act, so the two cannot drift.

| Endpoint                                | Capability    | Roles                       |
| --------------------------------------- | ------------- | --------------------------- |
| `GET /v1/*` — every read                | `read`        | Any                         |
| `PATCH /v1/contacts/{id}/token`         | `grant`       | Owner, Admin                |
| `PATCH /v1/leads/{id}`                  | `grant`       | Owner, Admin                |
| `PATCH /v1/handoffs/{id}`               | `reply`       | Owner, Admin, Responder     |
| `PATCH /v1/guardrails`                  | `manage_team` | Owner, Admin                |
| `PATCH /v1/guardrails/stripe`           | `manage_team` | Owner, Admin                |
| `POST /v1/kill-switch`                  | `kill_switch` | Owner, Admin, **Responder** |
| `POST /api/mcp/{workspace}`             | `read`        | Any                         |
| `POST /api/mcp/{workspace}/u/{contact}` | `reply`       | Owner, Admin, Responder     |

<Note>
  `PATCH /v1/contacts/{id}/token` sits on `grant` rather than something weaker
  because it is the same class of act as the contact page's "issue access": it
  puts a credential for one of your users into Peeve. A credential that may only
  read must not be able to do that.
</Note>

<Note>
  The **per-contact MCP endpoint** needs `reply` when called with a user token —
  a teammate pointing their assistant at one customer is the same act as picking
  up that customer's hand-off in the console, so it needs the same reach and no
  more. Viewer and Developer are refused outright.
</Note>

`POST /v1/users` and `POST /v1/answer` take a
[secret key](/authentication/credentials) rather than a user token, so the role
matrix does not apply to them.

## When a role is insufficient

A `403` that names both the role you resolved to and the capability you lacked —
the two facts needed to fix it.

```json theme={null}
{
  "error": {
    "type": "invalid_request_error",
    "code": "insufficient_role",
    "message": "This credential acts as 'viewer', which cannot change workspace settings (owner or admin). A user token carries its owner's workspace role, so this needs a teammate whose role holds 'manage_team'."
  },
  "request_id": "req_xxx"
}
```

<Warning>
  The fix is **not** to mint a more powerful token. A user token carries its
  owner's role; there is no way to issue yourself a wider one. The fix is either
  a role change in **Settings → Team**, or a token from a teammate who already
  holds the capability.
</Warning>

## Unrecognised roles fail closed

Any stored role value that is not one of the five folds to **Viewer**, the
least-privilege role, so a bad value can never widen reach. The legacy `member`
value maps to `responder`, which is the reach it historically carried.

## Every call is attributed

Each API and MCP call is logged with the **token id** — never the token — plus
the user, the role that resolved, the action, the filters used and the result
count. Configuration writes also record before and after.

That is what lets a workspace answer "who changed this", and it is why MCP
requires a named credential rather than a shared one.

Free-text and email lookups are recorded as `<redacted>`: they would otherwise
carry your own customers' personal data into the log.
