Skip to main content

Installation

npm install crovver-node
# or
pnpm add crovver-node
# or
yarn add crovver-node
Requirements: Node.js 18+. TypeScript is optional but fully supported.

Initialize the Client

import { CrovverClient } from "crovver-node";

const crovver = new CrovverClient({
  apiKey: process.env.CROVVER_SECRET_KEY!,
});

Configuration Options

OptionTypeDefaultDescription
apiKeystringrequiredYour secret API key (sk_live_...)
timeoutnumber30000Request timeout in milliseconds
maxRetriesnumber3Retry attempts for server errors
debugbooleanfalseLog requests and responses
loggerfunctionundefinedCustom logger: (message, data?) => void

Tenant Management

// Create a tenant (B2B: workspace or user)
const tenant = await crovver.createTenant({
  externalTenantId: "workspace_abc123",
  name: "Acme Corp",
  externalUserId: "user_123",
});

// Retrieve a tenant
const tenant = await crovver.getTenant("workspace_abc123");

Plans

const { plans } = await crovver.getPlans();

for (const plan of plans) {
  console.log(`${plan.name} - $${plan.priceAmount / 100}/mo`);
}

Subscriptions

const { subscriptions } = await crovver.getSubscriptions("workspace_abc123");

// status: active | trial | past_due | pending_cancel | canceled | expired
console.log(subscriptions[0].status);

// Cancel a subscription
await crovver.cancelSubscription(subscriptionId, "workspace_abc123", "User requested cancellation");

Feature Entitlements

// Single-product org
const canAccess = await crovver.canAccess("workspace_abc123", "advanced_analytics");

// Multi-product: scope to a specific product
const canAccess = await crovver.canAccess("workspace_abc123", "advanced_analytics", "product-a");

// Subscription-existence check — does this tenant have any active sub for product-a?
const hasProduct = await crovver.canAccess("workspace_abc123", undefined, "product-a");

Credits

Credits are the preferred way to track and gate consumption in Crovver. Each credit pool has a key (e.g. "ai_generations") and is refilled by plan entitlements or add-on purchases.

Check balance

const balance = await crovver.credits.balance({ tenantId: "workspace_abc123" });

for (const pool of balance.pools) {
  console.log(`${pool.poolKey}: ${pool.remaining} remaining`);
}

Consume credits

consume is idempotent — passing the same idempotencyKey twice returns the original result without double-deducting.
const result = await crovver.credits.consume({
  tenantId: "workspace_abc123",
  poolKey: "ai_generations",
  amount: 1,
  idempotencyKey: crypto.randomUUID(), // stable per user action
  metadata: { prompt: "summarise report" }, // optional
});

if (result.result === "success") {
  console.log(`Remaining: ${result.remaining}`);
} else {
  // result.result === "insufficient_credits"
  // Prompt the user to purchase an add-on or upgrade
}
ConsumeCreditsRequest fields:
FieldTypeRequiredDescription
tenantIdstringYesYour tenant/workspace ID
poolKeystringYesCredit pool identifier defined in your plan
amountnumberYesUnits to deduct
idempotencyKeystringYesUnique key per action; prevents double-deduction
metadataobjectNoArbitrary key/value pairs stored with the event
ConsumeResponse fields:
FieldTypeDescription
result"success" | "insufficient_credits"Outcome of the deduction
remainingnumberCredits left in the pool after deduction
alreadyProcessedbooleantrue when the idempotency key was already seen
poolKeystringEchoed pool key

Add-ons

Add-ons let tenants purchase extra credit packs without changing their base plan.

List available add-ons

const addons = await crovver.addons.listAvailable("workspace_abc123");

for (const addon of addons) {
  console.log(`${addon.name}: ${addon.creditQty} credits — ${addon.currency} ${addon.amount}`);
}

Purchase an add-on

const purchase = await crovver.addons.purchase("workspace_abc123", {
  addonId: "addon_uuid",
  currency: "USD",
  idempotencyKey: crypto.randomUUID(),
  successUrl: "https://yourapp.com/billing/success",
  cancelUrl: "https://yourapp.com/billing/cancel",
});

if (purchase.requiresPayment) {
  // Redirect to Stripe checkout
  window.location.href = purchase.checkoutUrl;
} else {
  // Credits added immediately (free add-on or already processed)
  console.log(`${purchase.creditQty} credits added`);
}

Get active add-on credits

const active = await crovver.addons.getActive("workspace_abc123");

for (const pool of active) {
  console.log(`${pool.poolKey}: ${pool.remaining} remaining`);
}

Usage Tracking (deprecated)

recordUsage and checkUsageLimit are deprecated. They write to a simple event log with no idempotency and do not integrate with the credit pool system. Use credits.consume instead.
// ❌ Deprecated — use credits.consume() instead
await crovver.recordUsage("workspace_abc123", "api_calls", 1);

// ❌ Deprecated — use credits.balance() instead
const limit = await crovver.checkUsageLimit("workspace_abc123", "api_calls");
console.log(`${limit.current} / ${limit.limit}`);

Checkout

const session = await crovver.createCheckoutSession({
  externalTenantId: "workspace_abc123",
  planId: "plan_pro",
  successUrl: "https://yourapp.com/billing/success",
  cancelUrl: "https://yourapp.com/billing/cancel",
});

// Redirect user to session.checkoutUrl

Invoices

const { invoices } = await crovver.getInvoices("workspace_abc123");

Error Handling

import { CrovverClient, CrovverError } from "crovver-node";

try {
  const canAccess = await crovver.canAccess("workspace_abc123", "advanced_analytics");
} catch (error) {
  if (error instanceof CrovverError) {
    error.message;      // Human-readable message
    error.statusCode;   // HTTP status code
    error.code;         // API error code string
    error.isRetryable;  // Whether the SDK already retried
  }
}
5xx errors, 429, and 408 responses are retried automatically with exponential backoff. Checkout endpoints are never retried to prevent duplicate charges.