npm package: @keychains/client-sdk
Description: Client SDK for Keychains.dev — make authenticated API calls through the Keychains proxy
Author: Interagentic Inc.
Latest version: 0.0.9

# @keychains/client-sdk

Minimal, zero-dependency SDK for making authenticated API calls through the [Keychains.dev](https://keychains.dev) proxy. Your code never touches real credentials — the proxy injects them at runtime.

## Quickstart

### 1. Install

```bash
npm install @keychains/client-sdk
```

### 2. Run with a fresh token

The `keychains token` command registers your machine (if needed), creates a wildcard permission, and mints a short-lived proxy token — all in one step:

```bash
KEYCHAINS_TOKEN=$(npx -y keychains token) \
  node your_script.js
```

### 3. Write your script

Use `keychainsFetch` as a drop-in replacement for `fetch`. The only difference? You can replace any credential with a template variable:

```typescript
import { keychainsFetch } from '@keychains/client-sdk';

// Gmail — get last 10 emails from my inbox
const res = await keychainsFetch(
  'https://gmail.googleapis.com/gmail/v1/users/me/messages?maxResults=10',
  {
    headers: {
      Authorization: 'Bearer {{OAUTH2_ACCESS_TOKEN}}',
    },
  },
);

const emails = await res.json();
console.log(emails);
```

That's it. The proxy resolves `{{OAUTH2_ACCESS_TOKEN}}` with the user's real Google OAuth token — your code never sees it.

### Running multiple scripts

Tokens expire after **15 minutes**. To reuse the same token across multiple commands in a shell session, use `eval`:

```bash
eval $(npx -y keychains token --env)
# KEYCHAINS_TOKEN is now set for the next 15 minutes
node script_a.js
node script_b.js
```

---

## Template Variables

### How to write them

Template variables use the `{{VARIABLE_NAME}}` syntax. The variable name tells the proxy which type of credential to inject:

| Prefix | Type | Supported Variables |
|--------|------|---------------------|
| `OAUTH2_` | OAuth 2.0 token | `{{OAUTH2_ACCESS_TOKEN}}`, `{{OAUTH2_REFRESH_TOKEN}}` |
| `OAUTH1_` | OAuth 1.0 token | `{{OAUTH1_ACCESS_TOKEN}}`, `{{OAUTH1_REQUEST_TOKEN}}` |
| Anything else | API key | `{{LIFX_PERSONAL_ACCESS_TOKEN}}`, `{{OPENAI_API_KEY}}`, etc. |

### Where to put them

Place them exactly where you'd normally put the real credential — **headers**, **body**, or **query parameters**:

```typescript
// In a header (most common)
await keychainsFetch('https://api.lifx.com/v1/lights/all', {
  headers: { Authorization: 'Bearer {{LIFX_PERSONAL_ACCESS_TOKEN}}' },
});

// In the request body
await keychainsFetch('https://slack.com/api/chat.postMessage', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer {{OAUTH2_ACCESS_TOKEN}}',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ channel: '#general', text: 'Hello!' }),
});

// In query parameters
await keychainsFetch(
  'https://api.example.com/data?api_key={{MY_API_KEY}}&format=json',
);

// Multi-account: target a specific account by identifier
await keychainsFetch('https://api.github.com/user', {
  headers: { Authorization: 'Bearer {{OAUTH2_ACCESS_TOKEN}}' },
  account: 'work@company.com',
});
```

---

## What Happens Next

When you call `keychainsFetch`:

1. **URL rewriting** — `https://api.lifx.com/v1/lights/all` becomes `https://keychains.dev/api.lifx.com/v1/lights/all`
2. **Token injection** — your permission token is sent via `X-Proxy-Authorization` so the proxy knows who you are
3. **Scope check** — the proxy verifies the user has approved the required credentials for this API
4. **Credential resolution** — the proxy replaces `{{LIFX_PERSONAL_ACCESS_TOKEN}}` with the real API key stored in the user's vault
5. **Request forwarding** — the proxy forwards the request to the upstream API with real credentials injected
6. **Response passthrough** — the upstream response is returned to you as-is

### Handling missing approvals

With wildcard permissions, users approve scopes on demand. The first time your code hits a new API, the user may not have approved it yet. When that happens, `keychainsFetch` throws an `InsufficientScopeError` containing an `approvalUrl` — share it with the user so they can grant access:

```typescript
import { keychainsFetch, InsufficientScopeError } from '@keychains/client-sdk';

try {
  const res = await keychainsFetch('https://api.github.com/user', {
    headers: { Authorization: 'Bearer {{OAUTH2_ACCESS_TOKEN}}' },
  });
  console.log(await res.json());
} catch (err) {
  if (err instanceof InsufficientScopeError) {
    // The user hasn't approved GitHub yet — show them the link
    console.log('Please approve access:', err.approvalUrl);
    // Once approved, retry the same call and it will succeed
  }
}
```

The error includes useful details:

| Property        | Type       | Description |
|-----------------|------------|-------------|
| `approvalUrl`   | `string?`  | URL the user should visit to approve the missing scopes |
| `missingScopes` | `string[]?`| Scopes that need approval |
| `refusedScopes` | `string[]?`| Scopes explicitly refused by the user |
| `code`          | `string`   | Error code (`insufficient_scope`, `scope_refused`, `permission_denied`, etc.) |

### Security benefits

- Secrets never leave the Keychains.dev servers — your code, logs, and environment stay clean
- Users approve exactly which scopes and APIs an agent can access
- Credentials can only be sent to the APIs of the providers they belong to
- Every proxied request is audited with full traceability
- Permissions can be revoked instantly from the [dashboard](https://keychains.dev/dashboard)

---

## Other SDKs

We also offer **[@keychains/machine-sdk](https://www.npmjs.com/package/@keychains/machine-sdk)** for standalone machines and long-running agents. It handles machine registration, permission creation, token minting, and delegation — all from Node.js, without the CLI.

---

## Bug Reports & Feedback

Found a bug or have a suggestion? Submit it straight from your terminal:

```bash
# Report a bug
npx -y keychains feedback "The proxy returns 502 on large POST bodies"

# Send feedback
npx -y keychains feedback --type feedback "Love the wildcard permissions!"

# With more detail
npx -y keychains feedback --type bug \
  --title "502 on large POST" \
  --description "When sending >1MB body to Slack API..." \
  --contact you@example.com
```

The `keychains feedback` command (alias: `keychains bug`) sends your report directly to the engineering team.

---

## More Info

Let's meet on [keychains.dev](https://keychains.dev)!