> ## Documentation Index
> Fetch the complete documentation index at: https://docs.toolshed.philo.ventures/llms.txt
> Use this file to discover all available pages before exploring further.

# Token Vending

> Resolve a credential for a tool plugin from connections, OAuth, or env vars.

## Vend a token

```
POST /api/tokens/vend
```

Thin wrapper around Better Auth's `getAccessToken()`. This is the legacy entry point used by the stdio runtime; the HTTP MCP transport calls `resolveToken()` directly (see below).

| Parameter  | In   | Required | Description                                   |
| ---------- | ---- | -------- | --------------------------------------------- |
| `userId`   | body | Yes      | User ID                                       |
| `provider` | body | Yes      | OAuth provider name (e.g. `github`, `google`) |

**Success response (200):**

```json theme={null}
{
  "accessToken": "gho_...",
  "refreshed": false
}
```

**Error responses:**

| Status | Code          | Description                           |
| ------ | ------------- | ------------------------------------- |
| 400    | —             | Missing `userId` or `provider`        |
| 404    | `NOT_LINKED`  | No connection found for this provider |
| 500    | `VEND_FAILED` | Token retrieval failed                |

## `resolveToken()` — three-source resolution

Inside the HTTP MCP transport (`apps/server/src/routes/mcp-remote.ts`), every plugin handler's `ctx.auth.getToken("<provider>")` runs through `resolveToken(userId, provider)` which checks **three** sources in order:

1. **`tool_connections` row** for the user with `tool_id = <provider>`. If present, decrypt and return the stored secret. This is how dashboard-managed API keys (granola personal/enterprise) and any future per-user tokens work.
2. **Better Auth `getAccessToken({ providerId, userId })`**. Returns the OAuth access token (auto-refreshing if expired). Covers the social providers (github/google/slack/linear) and Generic OAuth providers (quickbooks/gcp/docusign/carta).
3. **`process.env["${UPPER(provider).replace(/-/g, "_")}_API_KEY"]`**. Falls back to a server-wide env var. This is how the built-in tools (attio, browserbase, firecrawl, perplexity) get their keys without per-user storage.

If all three miss, `resolveToken()` throws `No credentials for "<provider>". Connect it in the Toolshed dashboard.` — surfaced to the agent verbatim by `call_tool`.

## Plugin perspective

Plugin handlers don't need to know which source supplies the token. They just call:

```typescript theme={null}
const token = await ctx.auth.getToken("granola-personal")
```

For OAuth tokens the plugin receives a Bearer access token; for API keys it receives the raw key string. The plugin's job is to put it in the correct header for the upstream API.
