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
| Option | Type | Default | Description |
|---|
apiKey | string | required | Your secret API key (sk_live_...) |
timeout | number | 30000 | Request timeout in milliseconds |
maxRetries | number | 3 | Retry attempts for server errors |
debug | boolean | false | Log requests and responses |
logger | function | undefined | Custom 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:
| Field | Type | Required | Description |
|---|
tenantId | string | Yes | Your tenant/workspace ID |
poolKey | string | Yes | Credit pool identifier defined in your plan |
amount | number | Yes | Units to deduct |
idempotencyKey | string | Yes | Unique key per action; prevents double-deduction |
metadata | object | No | Arbitrary key/value pairs stored with the event |
ConsumeResponse fields:
| Field | Type | Description |
|---|
result | "success" | "insufficient_credits" | Outcome of the deduction |
remaining | number | Credits left in the pool after deduction |
alreadyProcessed | boolean | true when the idempotency key was already seen |
poolKey | string | Echoed 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.