Skip to main content

TypeScript SDK

Installation

npm install @bkey/sdk
For server-side token verification you also want @bkey/node:
npm install @bkey/node

Biometric approval in one line

This is the whole pattern — drop it anywhere you need a human in the loop.
import { BKey } from '@bkey/sdk';

const bkey = new BKey({
  apiUrl: 'https://api.bkey.id',
  clientId: process.env.BKEY_CLIENT_ID!,
  clientSecret: process.env.BKEY_CLIENT_SECRET!,
  did: process.env.BKEY_USER_DID!,
});

// Blocks until the user approves on their phone or denies.
const result = await bkey.approve('Deploy api-gateway@abc123 to production', {
  scope: 'approve:deploy',
});

if (!result.approved) {
  return { error: 'Denied on device' };
}

// result.accessToken is an EdDSA-signed JWT — always verify it server-side
// before acting on the approval.
A single bkey.approve(...) call initiates CIBA, sends the push notification, and polls for the signed token. You never deal with the two-step protocol directly.

Verify the token before trusting it

The boolean approved tells you the flow completed. verifyToken() tells you the token is real:
import { verifyToken } from '@bkey/node';

const claims = await verifyToken(result.accessToken, {
  issuer: 'https://api.bkey.id',
  scope: 'approve:deploy',
});

// claims.sub   — verified DID of the user who approved
// claims.jti   — unique token ID, use for replay protection
// claims.scope — scopes actually granted
runDeploy({ service, ref, approvedBy: claims.sub, jti: claims.jti });
See MCP integration for why verifyToken is load-bearing and what it checks.

Anywhere a scope fits

Use caseScopeExample binding message
Deployapprove:deploy"Deploy api-gateway@abc123 to prod"
Refundapprove:payment"Refund $29.99 to customer@example.com"
DB dropapprove:action"Drop table users_archive (irreversible)"
Adminapprove:action"Grant admin role to alice@corp"
Vault readapprove:action"Read OPENAI_API_KEY"
Register a tight scope per sensitive action — it shows up on the user’s phone and prevents a token issued for one action from being replayed against another.

Structured action details

For anything the user should see in detail — amounts, recipients, resources — pass actionDetails:
const result = await bkey.approve('Refund $29.99 order #A-1234', {
  scope: 'approve:payment',
  actionDetails: {
    type: 'refund',
    description: 'Refund for order #A-1234',
    amount: 29.99,
    currency: 'USD',
    recipient: 'customer@example.com',
  },
});

Checkout

For e-commerce checkouts, use createCheckoutRequest — it wraps CIBA with checkout-specific fields:
const checkout = await bkey.createCheckoutRequest({
  merchantName: 'Example Store',
  items: [{ name: 'Widget', price: 9.99, quantity: 1 }],
  amount: 9.99,
  currency: 'USD',
});

const result = await bkey.getCheckoutRequestStatus(checkout.id);
if (result.status === 'completed') {
  fulfillOrder(result.paymentIntentId);
}

Vault

Request access to a stored secret (triggers biometric approval):
const access = await bkey.createAccessRequest({
  itemName: 'openai-api-key',
  fieldPath: 'value',
  purpose: 'API call to OpenAI',
  ephemeralPublicKey: myEphemeralX25519PublicKey, // base64
});
const result = await bkey.getAccessRequestStatus(access.id);
// result.e2eeCiphertext is X25519-ECDH + AES-256-GCM sealed to your ephemeral key
See the encryption guide for the exact ciphertext flow.

x402 payments

See the x402 guide for the full authorizeX402Payment flow.

Examples

API Reference

Full TypeDoc reference: SDK API Docs

Source

github.com/bkeyID/bkey/typescript/packages/sdk