JSPM

  • Created
  • Published
  • Downloads 181
  • Score
    100M100P100Q89280F
  • License MIT

Official JavaScript/TypeScript SDK for StackBE - the billing backend for your side project

Package Exports

  • @stackbe/sdk

Readme

@stackbe/sdk

Official JavaScript/TypeScript SDK for StackBE - the billing backend for your side project.

Installation

npm install @stackbe/sdk

Quick Start

import { StackBE } from '@stackbe/sdk';

const stackbe = new StackBE({
  apiKey: process.env.STACKBE_API_KEY!,
  appId: process.env.STACKBE_APP_ID!,
});

// Track usage
await stackbe.usage.track('customer_123', 'api_calls');

// Check entitlements
const { hasAccess } = await stackbe.entitlements.check('customer_123', 'premium_export');

// Create checkout session
const { url } = await stackbe.checkout.createSession({
  customer: 'cust_123',
  planId: 'plan_pro',
  successUrl: 'https://myapp.com/success',
});

// Get subscription
const subscription = await stackbe.subscriptions.get('cust_123');

// Send magic link
await stackbe.auth.sendMagicLink('user@example.com');

Modules

Usage Tracking

Track billable usage events for your customers:

// Track a single event
await stackbe.usage.track('customer_123', 'api_calls');

// Track multiple units
await stackbe.usage.track('customer_123', 'tokens', { quantity: 1500 });

// Check if within limits
const { allowed, remaining } = await stackbe.usage.check('customer_123', 'api_calls');
if (!allowed) {
  throw new Error('Usage limit exceeded');
}

// Get full usage summary
const usage = await stackbe.usage.get('customer_123');
console.log(usage.metrics);

// Track and check in one call
const result = await stackbe.usage.trackAndCheck('customer_123', 'api_calls');
if (!result.allowed) {
  // Handle limit exceeded
}

Entitlements

Check feature access based on customer's plan:

// Check single feature
const { hasAccess } = await stackbe.entitlements.check('customer_123', 'premium_export');

// Get all entitlements
const { entitlements, planName } = await stackbe.entitlements.getAll('customer_123');
// { premium_export: true, api_access: true, max_projects: 10 }

// Check multiple features at once
const features = await stackbe.entitlements.checkMany('customer_123', [
  'premium_export',
  'advanced_analytics',
]);

// Require a feature (throws if not available)
await stackbe.entitlements.require('customer_123', 'premium_export');

Checkout

Create Stripe checkout sessions:

// With existing customer ID
const { url } = await stackbe.checkout.createSession({
  customer: 'cust_123',
  planId: 'plan_pro_monthly',
  successUrl: 'https://myapp.com/success',
  cancelUrl: 'https://myapp.com/pricing',
});

// Redirect to checkout
res.redirect(url);

// With new customer (will be created)
const { url } = await stackbe.checkout.createSession({
  customer: { email: 'user@example.com', name: 'John' },
  planId: 'plan_pro_monthly',
  successUrl: 'https://myapp.com/success',
  trialDays: 14,
});

// Get checkout URL directly
const checkoutUrl = await stackbe.checkout.getCheckoutUrl({
  customer: 'cust_123',
  planId: 'plan_pro',
  successUrl: 'https://myapp.com/success',
});

Subscriptions

Manage customer subscriptions:

// Get current subscription
const subscription = await stackbe.subscriptions.get('cust_123');
if (subscription) {
  console.log(`Plan: ${subscription.plan.name}`);
  console.log(`Status: ${subscription.status}`);
}

// Check if customer has active subscription
const isActive = await stackbe.subscriptions.isActive('cust_123');

// Cancel subscription (at end of billing period)
await stackbe.subscriptions.cancel('sub_123');

// Cancel immediately
await stackbe.subscriptions.cancel('sub_123', { immediate: true });

// Update subscription (change plan)
await stackbe.subscriptions.update('sub_123', {
  planId: 'plan_enterprise',
  prorate: true,
});

// Reactivate canceled subscription
await stackbe.subscriptions.reactivate('sub_123');

// List all subscriptions
const subscriptions = await stackbe.subscriptions.list('cust_123');

Authentication

Passwordless authentication with magic links:

// Send magic link
await stackbe.auth.sendMagicLink('user@example.com');

// With redirect URL
await stackbe.auth.sendMagicLink('user@example.com', {
  redirectUrl: 'https://myapp.com/dashboard',
});

// For localhost development
await stackbe.auth.sendMagicLink('user@example.com', {
  useDev: true,
});

// Verify magic link token (in your /verify route)
const { token } = req.query;
const result = await stackbe.auth.verifyToken(token);

// result includes tenant and org context:
// - customerId, email, sessionToken
// - tenantId (your StackBE tenant)
// - organizationId, orgRole (if in org context)
res.cookie('session', result.sessionToken, { httpOnly: true });
res.redirect('/dashboard');

// Get session from token (includes tenant context)
const session = await stackbe.auth.getSession(sessionToken);
if (session) {
  console.log(session.customerId);
  console.log(session.email);
  console.log(session.tenantId);       // Tenant context
  console.log(session.organizationId); // Org context (if applicable)
  console.log(session.subscription);
  console.log(session.entitlements);
}

// Check if authenticated
const isAuthenticated = await stackbe.auth.isAuthenticated(sessionToken);

Customer Management

// Get customer
const customer = await stackbe.customers.get('cust_123');

// Get by email
const customer = await stackbe.customers.getByEmail('user@example.com');

// Create customer
const newCustomer = await stackbe.customers.create({
  email: 'user@example.com',
  name: 'John Doe',
  metadata: { source: 'api' },
});

// Get or create (idempotent)
const customer = await stackbe.customers.getOrCreate({
  email: 'user@example.com',
  name: 'John Doe',
});

// Update customer
await stackbe.customers.update('cust_123', { name: 'Jane Doe' });

Express Middleware

Track Usage Automatically

app.use(stackbe.middleware({
  getCustomerId: (req) => req.user?.customerId,
  metric: 'api_calls',
  skip: (req) => req.path === '/health',
}));

Require Feature Entitlements

app.get('/api/export',
  stackbe.requireFeature({
    getCustomerId: (req) => req.user?.customerId,
    feature: 'premium_export',
    onDenied: (req, res) => {
      res.status(403).json({ error: 'Upgrade to Pro' });
    },
  }),
  exportHandler
);

Enforce Usage Limits

app.use('/api',
  stackbe.enforceLimit({
    getCustomerId: (req) => req.user?.customerId,
    metric: 'api_calls',
    onLimitExceeded: (req, res, { current, limit }) => {
      res.status(429).json({ error: 'Rate limit exceeded', current, limit });
    },
  })
);

Authenticate Requests

app.use('/dashboard',
  stackbe.auth.middleware({
    getToken: (req) => req.cookies.session,
    onUnauthenticated: (req, res) => res.redirect('/login'),
  })
);

app.get('/dashboard', (req, res) => {
  // req.customer, req.subscription, req.entitlements are available
  res.json({ email: req.customer.email });
});

Next.js Integration

API Routes (App Router)

// app/api/generate/route.ts
import { StackBE } from '@stackbe/sdk';
import { NextResponse } from 'next/server';

const stackbe = new StackBE({
  apiKey: process.env.STACKBE_API_KEY!,
  appId: process.env.STACKBE_APP_ID!,
});

export async function POST(request: Request) {
  const { customerId } = await request.json();

  // Check limits
  const { allowed, remaining } = await stackbe.usage.check(customerId, 'generations');
  if (!allowed) {
    return NextResponse.json({ error: 'Limit reached' }, { status: 429 });
  }

  // Track usage
  await stackbe.usage.track(customerId, 'generations');

  // Do work...
  return NextResponse.json({ success: true, remaining: remaining! - 1 });
}

Server Actions

'use server';

import { StackBE } from '@stackbe/sdk';

const stackbe = new StackBE({
  apiKey: process.env.STACKBE_API_KEY!,
  appId: process.env.STACKBE_APP_ID!,
});

export async function exportData(customerId: string) {
  const { hasAccess } = await stackbe.entitlements.check(customerId, 'data_export');
  if (!hasAccess) {
    throw new Error('Upgrade to Pro to export data');
  }
  // Perform export...
}

Error Handling

The SDK provides typed error codes for specific error handling:

import { StackBE, StackBEError } from '@stackbe/sdk';

try {
  await stackbe.auth.verifyToken(token);
} catch (error) {
  if (error instanceof StackBEError) {
    // Handle specific error types
    switch (error.code) {
      case 'TOKEN_EXPIRED':
        return res.redirect('/login?error=expired');
      case 'TOKEN_ALREADY_USED':
        return res.redirect('/login?error=used');
      case 'SESSION_EXPIRED':
        return res.redirect('/login');
      case 'CUSTOMER_NOT_FOUND':
        return res.status(404).json({ error: 'Customer not found' });
      case 'USAGE_LIMIT_EXCEEDED':
        return res.status(429).json({ error: 'Limit exceeded' });
      default:
        return res.status(500).json({ error: 'Something went wrong' });
    }

    // Or use helper methods
    if (error.isAuthError()) {
      return res.redirect('/login');
    }
    if (error.isNotFoundError()) {
      return res.status(404).json({ error: error.message });
    }
  }
}

Error Codes

Category Codes
Auth TOKEN_EXPIRED, TOKEN_ALREADY_USED, TOKEN_INVALID, SESSION_EXPIRED, SESSION_INVALID, UNAUTHORIZED
Resources NOT_FOUND, CUSTOMER_NOT_FOUND, SUBSCRIPTION_NOT_FOUND, PLAN_NOT_FOUND, APP_NOT_FOUND
Usage USAGE_LIMIT_EXCEEDED, METRIC_NOT_FOUND
Entitlements FEATURE_NOT_AVAILABLE, NO_ACTIVE_SUBSCRIPTION
Validation VALIDATION_ERROR, MISSING_REQUIRED_FIELD, INVALID_EMAIL
Network TIMEOUT, NETWORK_ERROR, UNKNOWN_ERROR

Configuration

const stackbe = new StackBE({
  apiKey: 'sk_live_...',           // Required: Your API key
  appId: 'app_...',                // Required: Your App ID
  baseUrl: 'https://api.stackbe.io', // Optional: API base URL
  timeout: 30000,                   // Optional: Request timeout in ms

  // Session caching (reduces API calls)
  sessionCacheTTL: 120,            // Optional: Cache sessions for 2 minutes

  // Environment-aware redirects
  devCallbackUrl: 'http://localhost:3000/auth/callback', // Optional: Auto-use in dev
});

Session Caching

Enable session caching to reduce API calls on every request:

const stackbe = new StackBE({
  apiKey,
  appId,
  sessionCacheTTL: 120, // Cache for 2 minutes
});

// Cached calls won't hit the API
const session1 = await stackbe.auth.getSession(token); // API call
const session2 = await stackbe.auth.getSession(token); // From cache

// Manually invalidate cache
stackbe.auth.invalidateSession(token);
stackbe.auth.clearCache(); // Clear all

Environment-Aware Redirects

Auto-detect development environment for magic link redirects:

const stackbe = new StackBE({
  apiKey,
  appId,
  devCallbackUrl: 'http://localhost:3000/auth/callback',
});

// In development (NODE_ENV !== 'production'), uses devCallbackUrl automatically
await stackbe.auth.sendMagicLink('user@example.com');
// Redirects to: http://localhost:3000/auth/callback

// In production, uses your configured auth callback URL
// Or pass explicit redirectUrl to override
await stackbe.auth.sendMagicLink('user@example.com', {
  redirectUrl: 'https://myapp.com/custom-callback',
});

Webhooks

Typed webhook payloads for handling StackBE events:

import type {
  AnyWebhookEvent,
  SubscriptionCreatedEvent,
  SubscriptionCancelledEvent,
  PaymentFailedEvent,
} from '@stackbe/sdk';

// In your webhook handler
app.post('/webhooks/stackbe', (req, res) => {
  const event = req.body as AnyWebhookEvent;

  switch (event.type) {
    case 'subscription_created':
      const sub = event as SubscriptionCreatedEvent;
      console.log(`New subscription: ${sub.data.planName}`);
      break;

    case 'subscription_cancelled':
      const cancelled = event as SubscriptionCancelledEvent;
      console.log(`Cancelled: ${cancelled.data.id}`);
      break;

    case 'payment_failed':
      const payment = event as PaymentFailedEvent;
      console.log(`Payment failed: ${payment.data.failureReason}`);
      // Send dunning email
      break;
  }

  res.json({ received: true });
});

Webhook Event Types

Event Payload
subscription_created SubscriptionWebhookPayload
subscription_updated SubscriptionWebhookPayload
subscription_cancelled SubscriptionWebhookPayload
subscription_renewed SubscriptionWebhookPayload
trial_started SubscriptionWebhookPayload
trial_ended SubscriptionWebhookPayload
payment_succeeded PaymentWebhookPayload
payment_failed PaymentWebhookPayload
customer_created CustomerWebhookPayload
customer_updated CustomerWebhookPayload

TypeScript

Full type definitions included:

import type {
  Customer,
  Subscription,
  SubscriptionWithPlan,
  CheckoutSessionResponse,
  SessionResponse,
  TrackUsageResponse,
  CheckEntitlementResponse,
  StackBEErrorCode,
  WebhookEventType,
  AnyWebhookEvent,
} from '@stackbe/sdk';

License

MIT