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

# Per-user grants

> The revocable, per-contact MCP endpoint and its pv_grant_ bearer tokens.

```
GET  https://api.peeve.ai/api/mcp/{workspace}/u/{contact}
POST https://api.peeve.ai/api/mcp/{workspace}/u/{contact}
```

The same tools as [the workspace endpoint](/mcp/workspace-endpoint), behind a
bearer token bound to one contact. It exists so one of your end users can
connect their own assistant with a credential **you can revoke for that person
alone**, without affecting anyone else.

Advertised as `mcp.peeve.ai/{workspace}/u/{contact}`.

<Warning>
  The Growth plan gate applies here exactly as it does to the workspace
  endpoint, and the scope is hard: this endpoint **never reaches workspace
  tools**, only this one contact's own account.

  A teammate's API token is accepted here **only with `reply`** — owner, admin
  or responder. Viewer and developer are refused outright: pointing an assistant
  at one customer is the same act as picking up their hand-off in the console,
  and needs the same reach.
</Warning>

## The token

A grant token looks like `pv_grant_` followed by high-entropy random data. It is
bound to a specific `(workspace, contact)` pair, and is passed as a bearer
credential.

```bash theme={null}
curl -X POST https://api.peeve.ai/api/mcp/{workspace}/u/{contact} \
  -H "Authorization: Bearer pv_grant_xxx" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}'
```

A token is accepted only when every one of these holds:

* it starts with `pv_grant_`;
* it matches a grant on **this** workspace;
* that grant is bound to **this** contact;
* it has not been revoked;
* it has not expired.

Any failure returns the same `401` — the endpoint never tells a caller which
condition failed, or whether the grant exists.

<Note>
  Because the token is bound to both the workspace and the contact in the URL, a
  valid token for one contact cannot be used against another contact's endpoint,
  and a token from another workspace matches nothing.
</Note>

## Issuing a grant

Grants are issued from the dashboard, on the contact's detail page. The token is
shown once; hand it to that user through a channel you already trust for
credentials.

<Warning>
  Treat a grant token as a secret belonging to that one person. It is
  long-lived, it is a bearer credential, and it identifies them. Do not log it,
  put it in a URL, or send it over a channel you would not send a password over.
</Warning>

## Revoking a grant

Revoke from the same contact detail page. Revocation takes effect on the next
request — validation is a single indexed lookup with no cache in front of it, so
there is no propagation delay.

An expiry may also be set, and is checked on every request.

Peeve stamps a last-used timestamp on each successful call, so you can see which
grants are actually in use before you revoke.

## GET — discovery card

Requires the bearer token, like the POST.

```bash theme={null}
curl https://api.peeve.ai/api/mcp/{workspace}/u/{contact} \
  -H "Authorization: Bearer pv_grant_xxx"
```

```json theme={null}
{
  "name": "Example Corp · Peeve MCP (per-user)",
  "protocol": "mcp",
  "protocolVersion": "2024-11-05",
  "transport": "http-jsonrpc",
  "endpoint": "/api/mcp/00000000-0000-4000-8000-000000000000/u/11111111-1111-4111-8111-111111111111",
  "tools": 12,
  "hint": "POST JSON-RPC here with your pv_grant_ Bearer token."
}
```

## Errors

Identical to the workspace endpoint, with one addition.

| Condition                                  | HTTP | JSON-RPC code | Message                                         |
| ------------------------------------------ | ---- | ------------- | ----------------------------------------------- |
| Missing, invalid, revoked or expired token | 200  | `-32001`      | Invalid or revoked grant token.                 |
| Not entitled, disabled or killed           | 200  | `-32001`      | Agent channel not available for this workspace. |
| Rate limited                               | 429  | `-32002`      | Rate limited — slow down.                       |
| Platform unavailable                       | 503  | `-32001`      | Agent channel not available for this workspace. |

On `GET`, an auth failure is a plain `401 { "error": "unauthorized" }`; the
JSON-RPC framing above applies to `POST`.

A malformed workspace or contact UUID returns `404`.

## Rate limits

Bucketed per workspace and **per contact** — the identity the grant is bound to.
The token itself never goes in a limiter bucket, because it is a secret.

Ceilings follow your plan. This matters more here than on the workspace
endpoint: a grant is a long-lived token in a third-party assistant, and a
looping client would otherwise query your data indefinitely.

## Auditing

Every call is recorded against the contact, so the trail attributes each
`tools/call` to the specific revocable credential rather than to "someone with
the URL". The record carries the capability key, the method and the outcome —
never the arguments.

This is the main practical reason to prefer per-user grants over the
workspace endpoint when you can: the workspace endpoint tells you *that* a tool
was called, and per-user grants tell you *whose assistant* called it.
