npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@keychains/client-sdk

v0.0.9

Published

Client SDK for Keychains.dev — make authenticated API calls through the Keychains proxy

Readme

@keychains/client-sdk

Minimal, zero-dependency SDK for making authenticated API calls through the Keychains.dev proxy. Your code never touches real credentials — the proxy injects them at runtime.

Quickstart

1. Install

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:

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:

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:

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:

// 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: '[email protected]',
});

What Happens Next

When you call keychainsFetch:

  1. URL rewritinghttps://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:

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

Other SDKs

We also offer @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:

# 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 [email protected]

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


More Info

Let's meet on keychains.dev!