Authentication
Authenticate MCP clients with vTilt using OAuth 2.1 + PKCE (recommended) or a long-lived vtu_ personal API key.
The vTilt MCP server accepts two equally valid authentication paths. Pick whichever fits your client:
| Path | Best for | Header sent on every request |
|---|---|---|
| OAuth 2.1 + PKCE | Claude Desktop, ChatGPT Connector, Cursor, custom agents with a browser | Authorization: Bearer <JWT> (auto-refreshed) |
| Personal API key | Shell scripts, CI jobs, curl, legacy stdio bridges | Authorization: Bearer vtu_… (long-lived) |
Both paths grant the same access to the same projects you see in the dashboard. The rest of this page covers personal API keys — for OAuth, see the dedicated OAuth guide.
#1. Create a key
- Sign in to your vTilt dashboard.
- Open the user menu (top-right) and choose Personal API keys, or visit Account → Personal API keys directly.
- Click Create key.
- Give it a name that identifies the client and machine — e.g.
Cursor on laptop,Claude Desktop on home Mac,Codex CLI on prod-runner. - Pick an access level:
- Read-only (recommended) — the agent can list and inspect data through every read tool:
kpis-get,persons-list,events-recent,recordings-list, the VQL query layer, anddocs-search. This is the right choice for almost every workflow. - Read + write — adds the ability to mutate project state through write tools (
project-updatetoday; more on the way). Every write call is recorded in the MCP audit log. Pick this only if you actively need the agent to change state on your behalf.
- Read-only (recommended) — the agent can list and inspect data through every read tool:
- Click Create. The full secret (
vtu_…) is shown once. - Copy it now and paste it into your client. After you close the modal the secret cannot be recovered.
If you lose the secret, revoke the key (it stays in the list with a revokedAt timestamp for audit purposes) and create a new one. Old keys keep working until they're revoked.
#2. What a key can do
A vtu_ key inherits all the projects you can see in the dashboard. Keys are scoped:
| Field | Default |
|---|---|
scope.mode | mcp (the only value) |
scope.access | read (default) or write — picked by the Read-only / Read + write radio in the create modal |
scopedProjectIds | null — every project you can access |
expiresAt | null — no expiry by default |
When the public REST API ships, you'll be able to mint keys with scope.mode = 'rest'; that selector isn't surfaced yet because there's no public REST surface to call.
#3. Use a key
Pass the secret as a Bearer token. Every MCP client supports headers:
# Health check — verify the key works before configuring a client.
# `tools/list` is an MCP method that returns the tool registry.
curl -X POST 'https://www.vtilt.com/api/mcp' \
-H 'Authorization: Bearer vtu_YOUR_SECRET' \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {}
}'A successful response lists all tools the key can see (filtered by your access scope). If the response contains "error":{"code":-32001 or HTTP 401, the bearer header is missing, malformed, or revoked.
#4. Pin a project
If you have access to multiple projects, the server cannot guess which one you mean. Three ways to pin:
| Method | Where you set it | Lives until |
|---|---|---|
| Header | The MCP client's connection settings | The connection is reset |
| Query parameter | The MCP server URL itself | The connection is reset |
switch-project | The agent calls it during a conversation | 30 days (refreshed on use) |
Optional fourth header — x-vtilt-mcp-session-id: <uuid> — partitions Redis session pins when several MCP connections share the same user credential. See MCP server overview and the Cursor guide's subsection on parallel chats.
Examples:
# Header (Cursor, Claude Desktop, VS Code all expose a "headers" field)
x-vtilt-project-id: 7c5c1a3a-9d62-4e09-8a4f-9a3ce1b1f4a8
# Optional — isolate switch-project pins per MCP connection (UUID)
x-vtilt-mcp-session-id: f47ac10b-58cc-4372-a567-0e02b2c3d479
# Query parameter
https://www.vtilt.com/api/mcp?project_id=7c5c1a3a-9d62-4e09-8a4f-9a3ce1b1f4a8
# Session pin via tool call (the agent does this for you)
> "Switch me to the Marketing site project."You can find a project's id at Project settings → General in the dashboard. Single-project users don't need to pin — the server picks your only project automatically.
#5. Rotate and revoke
- Open Account → Personal API keys.
- Click Revoke next to the key. The row stays in the list (greyed out, with a
revokedAttimestamp) so you can audit which keys existed when. Revoked keys reject every subsequent request withunauthorized. - To rotate, create a new key first, swap it into your client, then revoke the old one.
#6. Common errors
| Error response | Meaning | Fix |
|---|---|---|
{"code":-32001,"message":"Unauthorized","data":{"reason":"missing_bearer"}} | No Authorization header | Configure your client's Authorization header |
{"code":-32001,"message":"Unauthorized","data":{"reason":"malformed_bearer"}} | Header present but not Bearer … | Prefix with Bearer (note the space) and try again |
{"code":-32001,"message":"Unauthorized","data":{"reason":"unknown_token"}} | Key not in the database | Re-copy from the dashboard, or create a new key |
{"code":-32001,"message":"Unauthorized","data":{"reason":"revoked"}} | Key was revoked | Create a new key |
{"code":-32001,"message":"Unauthorized","data":{"reason":"expired"}} | Key expiry passed | Create a new key |
{"code":-32001,"message":"Unauthorized","data":{"reason":"oauth_signature_invalid"}} | OAuth access token signature invalid | Reconnect your OAuth client |
{"code":-32001,"message":"Unauthorized","data":{"reason":"oauth_jwks_not_found"}} | JWKS endpoint returned 404 (wrong URL or routing) | Ensure GET https://www.vtilt.com/api/auth/jwks returns 200 on this host |
{"code":-32001,"message":"Unauthorized","data":{"reason":"oauth_audience_mismatch"}} | OAuth token's aud ≠ this MCP endpoint | Reconnect your OAuth client against this host |
{"code":-32001,"message":"Unauthorized","data":{"reason":"oauth_expired"}} | OAuth access token expired | Client should refresh automatically — reconnect if it doesn't |
{"code":-32001,"message":"Unauthorized","data":{"reason":"oauth_unknown_user"}} | OAuth token's sub doesn't map to a vTilt user | Reconnect and sign in again |
{"code":-32003,"message":"Forbidden","data":{"reason":"project_not_accessible"}} | Header/query pinned to a project you can't access | Remove the pin or pick a project from your dashboard |
{"code":-32099,"message":"Rate limit exceeded","data":{"retryAfter":42}} | 60-req/min sliding window exceeded | Back off and try again after retryAfter seconds |
#Next steps
- OAuth — the browser sign-in flow for modern MCP clients (recommended).
- Cursor — install in 30 seconds via the Cursor MCP catalogue or
~/.cursor/mcp.json. - Claude Desktop — connect via OAuth or the
mcp-remotebridge. - VS Code — drop into
.vscode/mcp.json. - Codex —
codex mcp addone-liner.