v1.0 - SaaS Enablement Layer

Add SaaS Features in Minutes

Sassy provides identity, tenancy, plans, entitlements, and team management - everything you need to add subscription-based features to your application.

Everything You Need for SaaS

Identity & Authentication

Secure user authentication with magic links, email verification, and JWT-based sessions. Works with your existing user database or standalone.

Multi-Tenancy

Full tenant isolation with automatic data segregation. Each app operates in its own tenant space with configurable settings.

Plans & Pricing

Define subscription plans with flexible pricing. Support for monthly/yearly billing, free trials, and custom pricing tiers.

Entitlements

JSON-based entitlements for fine-grained feature access. Check user permissions with a simple API call.

Team Management

Invite team members, manage roles, and control access. Built-in support for team-based subscriptions.

Billing Integration

Native integration with Stripe and Paddle. Handle subscriptions, invoices, and payment methods automatically.

Integration Guide

Quick Start

Get Sassy integrated into your application in under 5 minutes. Follow these steps to add authentication, plans, and entitlements to your app.

Prerequisites

You'll need your API Public Key from the Developer Portal. Create an app in the portal to get your credentials.

Step 1: Create an App

Log in to the Developer Portal and create a new application. You'll receive a Public Key that identifies your app.

Step 2: Define Your Plans

In the Developer Portal, create subscription plans with features and entitlements. For example:

JSON - Example Plan Entitlements
// Start Plan:
{
  "api_calls": 10000,
  "storage_gb": 50,
  "team_members": 10,
  "features": ["advanced_analytics", "api_access", "priority_support"]
}

Step 3: Integrate the SDK

Add the Sassy SDK to your application. See the Server SDK and Client SDK sections below for detailed integration guides.

Core Concepts

Apps

An App represents your application in Sassy. Each app has its own users, plans, and settings. You can create multiple apps for different environments (development, staging, production).

Users

Users are authenticated individuals within your app. They can have a subscription plan and belong to teams. User data is isolated per-app.

Plans

Plans define subscription tiers with pricing and features. Each plan has:

  • Name & Slug: Display name and unique identifier
  • Price & Interval: Cost and billing frequency (monthly/yearly)
  • Features: List of feature strings for display purposes
  • Entitlements: JSON object with quantified limits and capabilities

Entitlements

Entitlements are the permissions and limits associated with a plan. They're stored as JSON and can represent anything: API call limits, storage quotas, feature flags, etc.

Server SDK

The Server SDK provides secure backend integration for user management, entitlement checks, and subscription handling.

Installation

Bash
# npm
npm install @sassy/server

# yarn
yarn add @sassy/server

# pnpm
pnpm add @sassy/server

Initialization

JavaScript
import { Sassy } from '@sassy/server';

const sassy = new Sassy({
  apiUrl: 'https://your-sassy-instance.com',
  publicKey: 'pk_live_xxxxx'  // From Developer Portal
});

Authentication

Verify JWT tokens from authenticated users on your backend.

JavaScript
// Express middleware example
async function authMiddleware(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];

  if (!token) {
    return res.status(401).json({ error: 'Unauthorized' });
  }

  try {
    const user = await sassy.verifyToken(token);
    req.user = user;
    next();
  } catch (err) {
    return res.status(401).json({ error: 'Invalid token' });
  }
}

// Use in your routes
app.get('/api/protected', authMiddleware, (req, res) => {
  res.json({ user: req.user });
});

Headless Mode

Headless mode lets you build your own custom authentication UI while Sassy handles all backend auth logic, sessions, and security. Your server uses the SDK to forward auth requests to Sassy.

Architecture
┌─────────────────┐      ┌─────────────────┐      ┌─────────────────┐
│  Your Frontend  │ ──►  │   Your Server   │ ──►  │     Sassy       │
│  (Custom Auth   │      │ (Uses Server    │      │  (Auth Logic,   │
│   UI/Pages)     │      │  SDK)           │      │   Sessions)     │
└─────────────────┘      └─────────────────┘      └─────────────────┘

sassy.auth Methods

All headless authentication operations are available via sassy.auth:

Method Description
sassy.auth.signup() Create a new user account
sassy.auth.login() Authenticate with email/password
sassy.auth.logout() Invalidate a refresh token
sassy.auth.refreshToken() Get new tokens using refresh token
sassy.auth.verifySession() Verify token and get full session state
sassy.auth.forgotPassword() Request a password reset email
sassy.auth.resetPassword() Reset password with token
sassy.auth.verifyEmail() Verify email address with token
sassy.auth.requestMagicLink() Send a passwordless login link
sassy.auth.verifyMagicLink() Authenticate via magic link token
sassy.auth.switchTenant() Switch to a different tenant
sassy.auth.getFeatures() Get enabled auth features for the app

Signup & Login

JavaScript
// Signup - create new user
app.post('/api/auth/signup', async (req, res) => {
  const result = await sassy.auth.signup({
    email: req.body.email,
    password: req.body.password,
    name: req.body.name,
  });

  res.json({
    accessToken: result.accessToken,
    refreshToken: result.refreshToken,
    user: result.user,
    tenantId: result.tenantId,
  });
});

// Login - authenticate user
app.post('/api/auth/login', async (req, res) => {
  const result = await sassy.auth.login({
    email: req.body.email,
    password: req.body.password,
  });

  res.json({
    accessToken: result.accessToken,
    refreshToken: result.refreshToken,
    user: result.user,
    memberships: result.memberships,
  });
});

Token Management

JavaScript
// Refresh tokens
app.post('/api/auth/refresh', async (req, res) => {
  const result = await sassy.auth.refreshToken({
    refreshToken: req.body.refreshToken,
  });

  res.json({
    accessToken: result.accessToken,
    refreshToken: result.refreshToken,
  });
});

// Logout - invalidate refresh token
app.post('/api/auth/logout', async (req, res) => {
  await sassy.auth.logout(req.body.refreshToken);
  res.json({ success: true });
});

// Verify session - get full user/tenant/entitlements state
app.get('/api/auth/verify', async (req, res) => {
  const accessToken = req.headers.authorization?.replace('Bearer ', '');

  const session = await sassy.auth.verifySession({ accessToken });

  if (session.valid) {
    res.json({
      user: session.user,
      tenant: session.tenant,
      entitlements: session.entitlements,
    });
  } else {
    res.status(401).json({ error: session.reason });
  }
});

Password Reset

JavaScript
// Request password reset - always succeeds (doesn't reveal if user exists)
app.post('/api/auth/forgot-password', async (req, res) => {
  await sassy.auth.forgotPassword({ email: req.body.email });
  res.json({ message: 'If an account exists, a reset link has been sent.' });
});

// Reset password with token from email
app.post('/api/auth/reset-password', async (req, res) => {
  await sassy.auth.resetPassword({
    token: req.body.token,
    password: req.body.password,
  });
  res.json({ message: 'Password reset successfully' });
});

Magic Links (Passwordless)

JavaScript
// Request magic link
app.post('/api/auth/magic-link', async (req, res) => {
  await sassy.auth.requestMagicLink({ email: req.body.email });
  res.json({ message: 'If an account exists, a magic link has been sent.' });
});

// Verify magic link token
app.post('/api/auth/verify-magic-link', async (req, res) => {
  const result = await sassy.auth.verifyMagicLink({ token: req.body.token });

  res.json({
    accessToken: result.accessToken,
    refreshToken: result.refreshToken,
    user: result.user,
  });
});

Multi-Tenant Switching

JavaScript
// Switch to a different tenant (for users with multiple tenants)
app.post('/api/auth/switch-tenant', async (req, res) => {
  const accessToken = req.headers.authorization?.replace('Bearer ', '');

  const result = await sassy.auth.switchTenant({
    accessToken,
    tenantId: req.body.tenantId,
  });

  res.json({
    accessToken: result.accessToken,   // New token scoped to target tenant
    refreshToken: result.refreshToken,
  });
});

sassy.tenants Methods

Manage tenants, members, and settings via sassy.tenants:

Method Description
sassy.tenants.getCurrent() Get current tenant with entitlements
sassy.tenants.listUserTenants() List all tenants user belongs to
sassy.tenants.create() Create a new tenant
sassy.tenants.updateCurrent() Update current tenant name/slug
sassy.tenants.listMembers() List tenant members with roles
sassy.tenants.updateMemberRole() Change a member's role
sassy.tenants.removeMember() Remove member from tenant

sassy.invitations Methods

Manage team invitations via sassy.invitations:

Method Description
sassy.invitations.list() List pending invitations
sassy.invitations.create() Invite user by email with role
sassy.invitations.getInfo() Get public info for accept page
sassy.invitations.accept() Accept invitation and join tenant
sassy.invitations.revoke() Cancel pending invitation
sassy.invitations.resend() Resend invitation email

sassy.plans Methods

Retrieve subscription plans via sassy.plans:

Method Description
sassy.plans.list() Get all available plans with pricing
sassy.plans.get() Get specific plan by ID

Error Handling

JavaScript
import {
  ApiError,
  AuthenticationError,
  AccountLockedError,
  UserExistsError,
  InvalidInvitationError,
} from '@sassy/server-sdk';

app.post('/api/auth/signup', async (req, res) => {
  try {
    const result = await sassy.auth.signup({ ... });
    res.json(result);
  } catch (error) {
    if (error instanceof UserExistsError) {
      return res.status(409).json({ error: 'Email already registered' });
    }
    if (error instanceof AccountLockedError) {
      return res.status(429).json({ error: 'Account temporarily locked' });
    }
    if (error instanceof AuthenticationError) {
      return res.status(401).json({ error: 'Invalid credentials' });
    }
    res.status(500).json({ error: 'Internal server error' });
  }
});
Full Control

Headless mode gives you complete control over your auth UI while Sassy handles security, rate limiting, password hashing, token management, and session invalidation.

User Management

Fetch user data, update profiles, and manage subscriptions.

JavaScript
// Get user by ID
const user = await sassy.users.get(userId);

// Get user with their plan and entitlements
const userWithPlan = await sassy.users.getWithPlan(userId);
console.log(userWithPlan.plan);        // { name: 'Pro', slug: 'pro', ... }
console.log(userWithPlan.entitlements); // { api_calls: 10000, ... }

// Update user's plan
await sassy.users.updatePlan(userId, 'enterprise');

Entitlement Checks

Check if a user has access to specific features or enough quota.

JavaScript
// Check if user has a specific feature
const hasApiAccess = await sassy.entitlements.hasFeature(
  userId,
  'api_access'
);

if (!hasApiAccess) {
  return res.status(403).json({
    error: 'API access requires a Pro plan or higher'
  });
}

// Check numeric entitlement
const apiCallLimit = await sassy.entitlements.getValue(
  userId,
  'api_calls'
);
console.log(`User can make ${apiCallLimit} API calls`);

// Get all entitlements at once
const entitlements = await sassy.entitlements.getAll(userId);
// { api_calls: 10000, storage_gb: 50, features: [...] }
Best Practice

Cache entitlements on the server for frequently accessed features. The SDK provides built-in caching support.

Client SDK

The Client SDK handles authentication flows and provides user state management for your frontend application.

Installation

Include the SDK via script tag or import it in your build:

HTML - Script Tag
<!-- Include from your Sassy instance -->
<script src="https://your-sassy-instance.com/sdk.js"></script>

<script>
  const sassy = new SassySDK({
    publicKey: 'pk_live_xxxxx'
  });
</script>
JavaScript - ES Module
import { SassySDK } from '@sassy/client';

const sassy = new SassySDK({
  apiUrl: 'https://your-sassy-instance.com',
  publicKey: 'pk_live_xxxxx'
});

Login Flow

The SDK supports passwordless magic link authentication out of the box.

JavaScript
// Request a magic link
async function requestMagicLink(email) {
  try {
    await sassy.auth.sendMagicLink(email);
    showMessage('Check your email for the login link!');
  } catch (err) {
    showError(err.message);
  }
}

// Handle the magic link callback (on page load)
async function handleAuthCallback() {
  const params = new URLSearchParams(window.location.search);
  const token = params.get('token');

  if (token) {
    try {
      await sassy.auth.verifyToken(token);
      window.location.href = '/dashboard';
    } catch (err) {
      showError('Invalid or expired link');
    }
  }
}

// Logout
async function logout() {
  await sassy.auth.logout();
  window.location.href = '/';
}

User State

Access the current user's data, plan, and entitlements from the client.

JavaScript
// Check if user is logged in
if (sassy.isAuthenticated()) {
  const user = sassy.getUser();
  console.log(`Welcome, ${user.name}!`);
}

// Get user's current plan
const plan = sassy.getPlan();
console.log(`You're on the ${plan.name} plan`);

// Check entitlements for UI
const entitlements = sassy.getEntitlements();
if (entitlements.features?.includes('advanced_analytics')) {
  showAnalyticsDashboard();
} else {
  showUpgradePrompt();
}

// Listen for auth state changes
sassy.onAuthStateChange((user) => {
  if (user) {
    updateUI(user);
  } else {
    showLoginScreen();
  }
});
Security Note

Client-side entitlement checks are for UI purposes only. Always verify entitlements on the server for protected resources.

Architecture

Sassy follows a two-server model where Sassy is the authority and your backend is the enforcer:

System Architecture
┌─────────────────────────┐         ┌─────────────────────────┐
│     Sassy Server        │         │      Your Backend       │
│       (Authority)       │◄───────►│       (Enforcer)        │
├─────────────────────────┤         ├─────────────────────────┤
│ • User management       │         │ • App domain data       │
│ • Tenant management     │ Webhooks│ • Business logic        │
│ • Plans & entitlements  │ ───────►│ • Enforce entitlements  │
│ • Team invitations      │         │ • Credit metering       │
│ • JWT issuance & JWKS   │ JWT     │ • Validate tokens (JWKS)│
│ • Embedded control panel│ ───────►│ • Apply Sassy webhooks  │
└─────────────────────────┘         └─────────────────────────┘

Your backend validates JWTs using Sassy's JWKS endpoint and receives webhooks when entitlements change. This means no per-request calls to Sassy - your app caches entitlements and validates tokens locally.

Webhooks

Sassy sends webhooks to your backend when important events occur. Configure your webhook endpoint in the Developer Portal.

JavaScript - Webhook Handler
import { webhookHandler } from '@sassy/server';

app.post('/webhooks/sassy',
  express.raw({ type: 'application/json' }),
  webhookHandler(),
  (req, res) => {
    const event = req.webhookEvent;

    switch (event.event_type) {
      case 'tenant.entitlements.updated':
        // Update local entitlements cache
        const { tenant_id, entitlements } = event.data;
        break;

      case 'membership.updated':
        // Update user role cache
        const { user_id, role } = event.data;
        break;

      case 'tenant.status.updated':
        // Handle status change (active, past_due, suspended)
        const { status } = event.data;
        break;
    }

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

Webhook Events

Event Description
tenant.entitlements.updated Tenant's entitlements changed (plan change, override added)
tenant.status.updated Tenant status changed (active, past_due, suspended)
membership.updated User's role in a tenant changed
membership.removed User removed from a tenant

JWT Token Claims

Sassy issues JWT tokens with the following claims that your backend can use for authorization:

Claim Description
sub User ID
tid Tenant ID
roles User's roles in the tenant (array)
ent_ver Entitlements version (for cache invalidation)
tenant_status Tenant status (active, past_due, suspended)
Token Verification

Use the JWKS endpoint at /api/auth/.well-known/jwks.json to verify tokens. The Server SDK handles this automatically.

API Reference

Full list of available API endpoints. All endpoints require authentication via the SDK or API key.

Authentication Endpoints

Endpoint Description
POST /api/auth/magic-link Request a magic link for email authentication
POST /api/auth/verify Verify a magic link token and get session
POST /api/auth/logout End the current session
GET /api/auth/me Get the current authenticated user
GET /api/auth/.well-known/jwks.json Get public keys for JWT verification

User Endpoints

Endpoint Description
GET /api/users/:id Get user by ID
PATCH /api/users/:id Update user profile
GET /api/users/:id/entitlements Get user's entitlements

Plan Endpoints

Endpoint Description
GET /api/plans List all available plans
GET /api/plans/:slug Get a specific plan by slug

Server Status

Current status of the Sassy server and API.

Server Checking...
API Checking...

Quick links: