Skip to main content
This is the URL GET /v1/contacts returns as a contact’s mcp_url — but only when that contact has a usable vendor token, since without one the channel can guide but cannot act on their account. The same tools as the 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.
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.

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

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

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.
The endpoint field is the app’s own path, not the friendly host — that is what the server returns. Keep using mcp.peeve.ai when handing the address to a client.

Errors

Identical to the workspace endpoint, with one addition. 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.