> ## Documentation Index
> Fetch the complete documentation index at: https://docs.crovver.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Node.js SDK

> Integrate Crovver into your Node.js backend.

## Installation

```bash theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
// 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

```typescript theme={null}
const { plans } = await crovver.getPlans();

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

## Subscriptions

```typescript theme={null}
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

```typescript theme={null}
// 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

```typescript theme={null}
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.

```typescript theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
const active = await crovver.addons.getActive("workspace_abc123");

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

***

## Usage Tracking (deprecated)

<Warning>
  `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`](#credits) instead.
</Warning>

```typescript theme={null}
// ❌ 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

```typescript theme={null}
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

```typescript theme={null}
const { invoices } = await crovver.getInvoices("workspace_abc123");
```

## Error Handling

```typescript theme={null}
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
  }
}
```

<Note>
  5xx errors, 429, and 408 responses are retried automatically with exponential backoff. Checkout endpoints are **never** retried to prevent duplicate charges.
</Note>
