Skip to main content
v2.2.0
April 13, 2026

New Features

License Management Module

A new client.license module provides full lifecycle management for software licenses.license.activate({ licenseKey, customerId?, instanceName?, metadata? })Activates a license key and creates a new instance. Returns a LicenseActivateResponse containing the instanceId, activatedAt, expiresOn, and the full LicenseDetails object.
const response = await client.license.activate({
    licenseKey: "LIC-XXXX-YYYY-ZZZZ",
    customerId: "cust_789",
    instanceName: "My MacBook Pro",
    metadata: { os: "macOS", arch: "arm64" },
});

console.log(response.instanceId);
console.log(response.license.activationUsage); // e.g. 1
console.log(response.license.plan.product.name); // e.g. "Kelviq Engine"
license.deactivate({ licenseKey, instanceId })Deactivates a specific license instance. Returns a LicenseDeactivateResponse with message and deactivatedAt.
const response = await client.license.deactivate({
    licenseKey: "LIC-XXXX-YYYY-ZZZZ",
    instanceId: "8f3e2b1a-5c6d-4e9f-8a0b-1c2d3e4f5g6h",
});
console.log(response.message); // "License instance deactivated successfully."
license.validate({ licenseKey, instanceId? })Validates a license key and optionally a specific instance. Returns a LicenseValidateResponse with valid, code, detail, metadata, and the full LicenseDetails.
const response = await client.license.validate({
    licenseKey: "LIC-XXXX-YYYY-ZZZZ",
    instanceId: "8f3e2b1a-5c6d-4e9f-8a0b-1c2d3e4f5g6h",
});

if (response.valid) {
    console.log(`Valid — code: ${response.code}`);
} else {
    console.log(`Invalid — ${response.detail}`);
}

New TypeScript Interfaces

  • LicenseDetails — Full license object with id, licenseKey, activatedOn, expiresOn, activationUsage, activationLimit, enabled, customer, plan, and subscription.
  • LicenseCustomer{ customerId, name?, email? } nested in LicenseDetails.
  • LicensePlan — Expanded with description, version, isLatest, and product (nested LicensePlanProduct).
  • LicensePlanProduct{ id, identifier, name, taxCode?, createdOn?, modifiedOn? }.
  • LicenseActivateResponse, LicenseDeactivateResponse, LicenseValidateResponse — Typed responses for each operation.

SubscriptionData New Fields

Three new fields derived from the subscription’s recurrence string are now included in SubscriptionData (returned within LicenseDetails.subscription and customer subscription summaries):
  • billingType"SUBSCRIPTION" if the plan has a recurrence, "ONE_TIME" otherwise.
  • recurrenceUnit — Integer unit from the recurrence string (e.g. 1 from "1 month"), or null.
  • recurrenceType — Recurrence period in uppercase (e.g. "MONTH"), or null.
v2.1.0
April 2, 2026

New Features

Customer Portal Module

A new client.portal module lets you create pre-authenticated customer portal sessions server-side and redirect customers directly to their self-serve portal.portal.createSession({ customerId })
const session = await client.portal.createSession({
    customerId: "cust_789",
});

// Redirect the customer — no login required
res.redirect(session.customerPortalUrl);
Returns CreatePortalSessionResponse with:
  • token — Session token authenticating the portal session.
  • email — The customer’s email address.
  • customerPortalUrl — Pre-authenticated URL to redirect the customer to.
v2.0.0
February 27, 2026

New Features

Entitlements Aggregation Engine

When a customer has multiple subscriptions, the API can return duplicate featureId entries. The SDK now automatically aggregates them into a single Entitlement object per feature:
  • Numeric fields (usageLimit, currentUsage, remaining) are summed across all entries
  • hasAccess is true if any raw entry grants access
  • hardLimit is true if any entry sets it
  • All raw entries are preserved in the .items[] array
const entitlement = await client.entitlements.getEntitlement({
  customerId: "cust_123",
  featureId: "email-sends",
});

// Aggregated values across all subscriptions
console.log(entitlement.hasAccess);    // true
console.log(entitlement.usageLimit);   // 1500 (e.g., 1000 from base plan + 500 from top-up)
console.log(entitlement.currentUsage); // 200
console.log(entitlement.remaining);    // 1300

// Per-subscription details
entitlement.items.forEach(item => {
  console.log(item.usageLimit, item.resetAt);
});

Entitlement Interface

New type representing an aggregated entitlement with an items: EntitlementDetail[] field containing the raw entries that were aggregated.

getRawEntitlement({ customerId, featureId })

Returns the raw API response for a specific feature (CheckEntitlementsResponse with customerId wrapper), without any aggregation:
const raw = await client.entitlements.getRawEntitlement({
  customerId: "cust_123",
  featureId: "email-sends",
});
// { customerId: "cust_123", entitlements: [/* raw entries for email-sends */] }

getRawEntitlements({ customerId })

Returns the raw API response for all entitlements, without any aggregation:
const raw = await client.entitlements.getRawEntitlements({
  customerId: "cust_123",
});
// { customerId: "cust_123", entitlements: [/* all raw entries */] }

client.subscription Deprecated Alias

A backward-compatible getter that maps client.subscription to client.subscriptions, so existing code continues to work during migration.

Breaking Changes

client.subscription Renamed to client.subscriptions

The subscriptions module now uses the plural form for consistency with other modules (client.customers, client.entitlements, etc.):
// Before
const sub = await client.subscription.get({ subscriptionId: "sub_123" });

// After
const sub = await client.subscriptions.get({ subscriptionId: "sub_123" });
The old client.subscription accessor still works as a deprecated alias.

getEntitlement() Returns Entitlement | null

Previously returned CheckEntitlementsResponse (the raw API shape with a customerId wrapper). Now returns a single aggregated Entitlement object with an .items[] array, or null if the feature is not found:
// Before
const response = await client.entitlements.getEntitlement({
  customerId: "cust_123",
  featureId: "email-sends",
});
// { customerId: "cust_123", entitlements: [...] }

// After
const entitlement = await client.entitlements.getEntitlement({
  customerId: "cust_123",
  featureId: "email-sends",
});
// { featureId: "email-sends", hasAccess: true, currentUsage: 200, ... }
For the original raw API response, use getRawEntitlement().

getAllEntitlements() Removed

Replaced by getEntitlements(), which returns Record<string, Entitlement> — a record keyed by featureId with aggregated values and an .items[] array:
// Before
const response = await client.entitlements.getAllEntitlements({ customerId: "cust_123" });

// After
const entitlements = await client.entitlements.getEntitlements({ customerId: "cust_123" });
// { "email-sends": Entitlement, "api-calls": Entitlement, ... }
For the original raw API response, use getRawEntitlements().

FeatureType Changed: "LIMIT""CUSTOMIZABLE"

The FeatureType union no longer includes "LIMIT". Update any code that matches on this value:
// Before
if (entitlement.featureType === "LIMIT") { ... }

// After
if (entitlement.featureType === "CUSTOMIZABLE") { ... }

resetAt Removed from Aggregated Entitlement

Since resetAt can differ across subscriptions, it is only available on individual items:
// Before
console.log(entitlement.resetAt);

// After
entitlement.items.forEach(item => {
  console.log(item.resetAt); // per-subscription reset date
});

Fixed

  • Fixed Python-style try/except syntax in subscription update documentation — replaced with JavaScript try...catch.
  • Added missing try/catch error handling to subscription cancel documentation example.
  • Removed invalid JSON comments (// Server-generated UUID) from documentation response blocks.
  • Fixed trailing comma in checkout session JSON response example.
  • Fixed "Node SDK Use" typo → "Node SDK User" in create customer documentation.
  • Fixed Truetrue, boolboolean in hasAccess documentation.
  • Replaced “Pydantic model” references with “TypeScript Interface” or “Object”.
  • Replaced “Dictionary” with “Object” in parameter descriptions.
  • Fixed Python-style ACCESS_TOKEN = "..."const ACCESS_TOKEN = "..."; in setup example.