Skip to main content
This guide walks you through installing Revstack, defining a simple billing config, and checking your first entitlement. By the end, you’ll have a working example that validates feature access.

Prerequisites

  • Node.js 18 or later
  • A Revstack account (sign up at app.revstack.dev)
  • Your Revstack Secret Key (found in your dashboard)

Step 1: Install the SDK

npm install @revstackhq/node @revstackhq/core
We’re using the Node SDK (@revstackhq/node) for this example. If you’re building a React or Next.js app, see the Installation guide for framework-specific instructions.

Step 2: Define Your Billing Config

Create a revstack.config.ts file in your project root:
revstack.config.ts
import { defineConfig, defineFeature, definePlan } from "@revstackhq/core";

// Define the features you want to gate
const features = {
  "api-calls": defineFeature({
    name: "API Calls",
    type: "metered",
    unit_type: "requests",
  }),
  "premium-support": defineFeature({
    name: "Premium Support",
    type: "boolean",
    unit_type: "count",
  }),
};

// Define your billing plans
export default defineConfig({
  features,
  plans: {
    // Free plan for new users
    free: definePlan<typeof features>({
      name: "Free",
      is_default: true,
      is_public: true,
      type: "free",
      status: "active",
      features: {
        "api-calls": { value_limit: 1000, reset_period: "monthly" },
        // premium-support not included (defaults to false)
      },
    }),

    // Paid plan with more limits
    pro: definePlan<typeof features>({
      name: "Pro",
      is_default: false,
      is_public: true,
      type: "paid",
      status: "active",
      prices: [
        {
          amount: 2900, // $29.00 in cents
          currency: "USD",
          billing_interval: "monthly",
          trial_period_days: 14,
        },
      ],
      features: {
        "api-calls": { value_limit: 100000, reset_period: "monthly" },
        "premium-support": { value_bool: true },
      },
    }),
  },
});
The definePlan<typeof features> pattern gives you autocomplete and compile-time validation. TypeScript will error if you reference a feature that doesn’t exist.

Step 3: Set Your Secret Key

Add your Revstack Secret Key to your environment variables:
.env
REVSTACK_SECRET_KEY=sk_live_...
Never commit your secret key to version control. Always use environment variables or a secrets manager.

Step 4: Initialize the Client

Create a Revstack client instance:
app.ts
import { Revstack } from "@revstackhq/node";

const revstack = new Revstack({
  secretKey: process.env.REVSTACK_SECRET_KEY!,
});

Step 5: Check an Entitlement

Use entitlements.check() to validate feature access:
app.ts
async function handleAPIRequest(customerId: string) {
  // Check if the customer can make API calls
  const { allowed, limit, usage, reason } = await revstack.entitlements.check(
    customerId,
    "api-calls"
  );

  console.log(`Allowed: ${allowed}`);
  console.log(`Limit: ${limit}`);
  console.log(`Current Usage: ${usage}`);
  console.log(`Reason: ${reason}`);

  if (!allowed) {
    return {
      error: "API quota exceeded. Please upgrade your plan.",
      limit,
      usage,
    };
  }

  // Report usage after successful API call
  await revstack.usage.report({
    customer_id: customerId,
    feature_id: "api-calls",
    delta: 1,
  });

  return { success: true };
}

// Example usage
handleAPIRequest("cus_abc123")
  .then(result => console.log(result))
  .catch(error => console.error(error));

Step 6: Run Your Code

Execute your script:
npx tsx app.ts
You should see output like:
Allowed: true
Limit: 1000
Current Usage: 0
Reason: allowed
{ success: true }

What Just Happened?

Let’s break down what the entitlement check does:
1

Customer Identification

The SDK identifies the customer by their ID (cus_abc123). In production, this comes from your auth system.
2

Plan Resolution

Revstack looks up the customer’s active subscription to determine which plan they’re on (e.g., “Free” or “Pro”).
3

Feature Lookup

The system finds the feature configuration for api-calls in the customer’s plan.
4

Quota Check

For metered features, Revstack compares current usage against the limit defined in the plan.
5

Result

Returns allowed: true/false along with context (limit, usage, reason).

Next: Add a Boolean Feature Check

Boolean features work the same way, but return a simple yes/no:
const { allowed } = await revstack.entitlements.check(
  customerId,
  "premium-support"
);

if (!allowed) {
  return { message: "Upgrade to Pro for 24/7 premium support" };
}

// Show premium support UI
return <PremiumSupportChat />;

Common Patterns

Gate a Feature in an API Route

app.get("/api/advanced-analytics", async (req, res) => {
  const customerId = req.user.id;

  const { allowed } = await revstack.entitlements.check(
    customerId,
    "advanced-analytics"
  );

  if (!allowed) {
    return res.status(403).json({
      error: "This feature requires a Pro plan",
      upgrade_url: "https://yourapp.com/pricing",
    });
  }

  const analytics = await generateAdvancedAnalytics(customerId);
  res.json(analytics);
});

Pre-Check Before Expensive Operations

async function generateAIResponse(customerId: string, prompt: string) {
  // Check quota BEFORE calling the AI API
  const { allowed, limit, usage } = await revstack.entitlements.check(
    customerId,
    "ai-tokens",
    { amount: 1000 } // Estimate token cost
  );

  if (!allowed) {
    throw new Error(
      `Insufficient AI tokens. Used ${usage}/${limit}. Upgrade to continue.`
    );
  }

  const response = await openai.chat.completions.create({
    model: "gpt-4",
    messages: [{ role: "user", content: prompt }],
  });

  // Report actual usage
  await revstack.usage.report({
    customer_id: customerId,
    feature_id: "ai-tokens",
    delta: response.usage.total_tokens,
  });

  return response;
}

Enforce Hard Limits

async function addTeamMember(organizationId: string, email: string) {
  // Check if adding one more seat is allowed
  const { allowed, limit, usage } = await revstack.entitlements.check(
    organizationId,
    "team-seats",
    { amount: 1 }
  );

  if (!allowed) {
    return {
      error: `Your plan includes ${limit} seats and ${usage} are in use. Upgrade to add more.`,
    };
  }

  // Add the team member
  await db.teamMembers.create({ organization_id: organizationId, email });

  // Increment seat usage
  await revstack.usage.report({
    customer_id: organizationId,
    feature_id: "team-seats",
    delta: 1,
  });

  return { success: true };
}

Error Handling

The SDK throws structured errors for different failure modes:
import { Revstack, RateLimitError, RevstackAPIError } from "@revstackhq/node";

try {
  await revstack.entitlements.check(customerId, "api-calls");
} catch (error) {
  if (error instanceof RateLimitError) {
    // Retry after error.retryAfter seconds
    console.log(`Rate limited. Retry after ${error.retryAfter}s`);
  } else if (error instanceof RevstackAPIError) {
    // API returned a non-2xx status
    console.error(`API error: ${error.status} - ${error.message}`);
  } else {
    // Network or configuration error
    console.error("Unexpected error:", error);
  }
}

Full Example

Here’s a complete working example with error handling:
example.ts
import { Revstack, RateLimitError } from "@revstackhq/node";

const revstack = new Revstack({
  secretKey: process.env.REVSTACK_SECRET_KEY!,
});

async function processAPIRequest(customerId: string, operation: string) {
  try {
    // 1. Check entitlement
    const { allowed, limit, usage, reason } = await revstack.entitlements.check(
      customerId,
      "api-calls"
    );

    if (!allowed) {
      return {
        success: false,
        error: `Access denied: ${reason}`,
        quota: { limit, usage },
      };
    }

    // 2. Perform the operation
    const result = await performOperation(operation);

    // 3. Report usage
    await revstack.usage.report({
      customer_id: customerId,
      feature_id: "api-calls",
      delta: 1,
    });

    return {
      success: true,
      data: result,
      quota: { limit, usage: usage + 1 },
    };
  } catch (error) {
    if (error instanceof RateLimitError) {
      return {
        success: false,
        error: "Service temporarily unavailable",
        retryAfter: error.retryAfter,
      };
    }
    throw error;
  }
}

async function performOperation(operation: string) {
  // Your business logic here
  return { result: `Processed: ${operation}` };
}

// Run it
processAPIRequest("cus_abc123", "data-export")
  .then(result => console.log(JSON.stringify(result, null, 2)))
  .catch(error => console.error(error));

Next Steps

Installation Guide

Set up Revstack for React, Next.js, or other frameworks

Core Concepts

Deep dive into entitlements, plans, and usage metering

API Reference

Complete documentation for all SDK methods

Billing as Code

Learn how to manage your config with the CLI

Build docs developers (and LLMs) love