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

# Node SDK

> Documentation for the Kelviq Node SDK

## Overview

The Kelviq Node SDK provides a convenient way to interact with the Kelviq REST APIs from your Node application.

## Installation

Install the Kelviq SDK using npm or yarn:

```shell theme={null}
npm install @kelviq/node-sdk
```

or

```shell theme={null}
yarn add @kelviq/node-sdk
```

## Prerequisites

Before you can initialize the client and use the SDK methods, you need a **Server API Key**.

You can obtain this key from the Kelviq application:

1. Navigate to Settings.
2. Go to the [API keys](https://app.kelviq.com/settings/api-keys) section.
3. Copy the **Server API Key**.

<Warning>
  **Security Warning:** The Server API Key should never be exposed in client-side code. Use it only in your backend services. For client-side applications, use the Client API Key with the [React SDK](/frontend-integration/react-sdk) or [JavaScript SDK](/frontend-integration/js-sdk).
</Warning>

Once copied, add this key to your environment variables (recommended for security):

```typescript theme={null}
// Example of setting it in your code (environment variables recommended)
const ACCESS_TOKEN = process.env.KELVIQ_SERVER_API_KEY;
```

## Configuring the Client

The SDK provides a main `Kelviq` client class. You instantiate it directly with your configuration options.

```typescript theme={null}

const ACCESS_TOKEN: string = "YOUR_SECRET_ACCESS_TOKEN";
```

#### How to Create a Client

```typescript theme={null}
import { Kelviq } from '@kelviq/node-sdk';

const client = new Kelviq({ accessToken: ACCESS_TOKEN });

```

## Supported Functionalities

The SDK currently supports the following operations:

1. [Customers](/backend-integration/node-sdk#customers)

2. [Checkout](/backend-integration/node-sdk#checkout)

3. [Entitlements](/backend-integration/node-sdk#entitlements)

4. [Reporting](/backend-integration/node-sdk#reporting)

5. [Subscriptions](/backend-integration/node-sdk#subscriptions)

6. [License](/backend-integration/node-sdk#license)

7. [Portal](/backend-integration/node-sdk#portal)

***

## Customers

The `customers` module allows you to manage customer records within Kelviq. You can access these operations via the `customers` attribute on an initialized `Kelviq` client instance.

***

### Creates a new customer

```typescript theme={null}
const newCustomer = await client.customers.create({
    customerId: "unique-customer-id-789",
    email: "new.node.customer@example.com",
    name: "Node SDK User",
    metadata: { source: "sdk_import", priority: "high" }
});
```

<Accordion title="Response">
  ```json theme={null}
  {
    "id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
    "customerId": "unique-customer-id-789",
    "name": "Node SDK User",
    "email": "new.node.customer@example.com",
    "details": {},
    "metadata": {
      "source": "sdk_import",
      "priority": "high"
    },
    "createdOn": "2025-06-04T06:03:30.195790Z",
    "modifiedOn": "2025-06-04T06:03:30.195831Z"
  }
  ```
</Accordion>

**Required Parameters:**

* `customerId` : A unique identifier for the customer that you define. This ID will be used to reference the customer in subsequent API calls.

**Optional Parameters:**

* `email` : The email address of the customer. Must be a valid email format.
* `name` : The name of the customer.
* `metadata` : An object of custom key-value pairs to store additional information about the customer.

**Returns:**
An instance of `CustomerResponse` (TypeScript Interface), representing the newly created customer record. Key attributes include:

* `id` : The server-generated unique UUID for the customer record.
* `customerId` : The client-provided customer identifier.
* `name` : The customer's name.
* `email` : The customer's email.
* `details` : Any server-added details about the customer (typically read-only).
* `metadata` : The metadata associated with the customer.
* `createdOn` : ISO 8601 timestamp of when the customer was created.
* `modifiedOn` : ISO 8601 timestamp of when the customer was last modified.

### Updates an existing customer

This operation performs a partial update (PATCH), so you only need to provide the fields you want to change.

```typescript theme={null}
const updatedCustomer = await client.customers.update({
  customerId: "unique-customer-id-789",
  name: "Node SDK User (Updated)",
  metadata: { tier: "premium_plus" }
});
```

<Accordion title="Response">
  ```json theme={null}
  {
    "id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
    "customerId": "unique-customer-id-789",
    "name": "Node SDK User (Updated)",
    "email": "new.node.customer@example.com",
    "details": {},
    "metadata": {
      "tier": "premium_plus"
    },
    "createdOn": "2025-06-04T06:03:30.195790Z",
    "modifiedOn": "2025-06-04T06:03:30.195831Z"
  }
  ```
</Accordion>

**Parameters:**

**Required Parameters:**

* `customerId` : A unique identifier for the customer that you define. This ID will be used to reference the customer in subsequent API calls.

**Optional Parameters:**

* `email` : The email address of the customer. Must be a valid email format.
* `name` : The name of the customer.
* `metadata` : An object of custom key-value pairs to store additional information about the customer.

**Returns:**
An instance of `CustomerResponse` (TypeScript Interface), representing the updated customer record. Key attributes include:

* `id` : The server-generated unique UUID for the customer record.
* `customerId` : The client-provided customer identifier.
* `name` : The customer's name.
* `email` : The customer's email.
* `details` : Any server-added details about the customer (typically read-only).
* `metadata` : The metadata associated with the customer.
* `createdOn` : ISO 8601 timestamp of when the customer was created.
* `modifiedOn` : ISO 8601 timestamp of when the customer was last modified.

***

## Checkout

The checkout module provides functionalities for creating and managing checkout sessions. You can access these operations via the checkout attribute on an initialized Kelviq client instance.

This operation creates a new checkout session for a customer, allowing them to proceed with a purchase or subscription.

```typescript theme={null}
import { Kelviq, CHARGE_PERIOD_CHOICES } from '@kelviq/node-sdk';

const response = await client.checkout.createSession({
  planIdentifier: "plan-pro-monthly",
  chargePeriod: CHARGE_PERIOD_CHOICES.MONTHLY,
  successUrl: "https://yourwebsite.com/checkout/success",
  customerId: "cust_789",  // Recommended, otherwise it will create a new customer
});

```

<Accordion title="Response">
  ```json theme={null}
   {
        "checkoutUrl": "https://www.kelviq.com/checkout/cs_fBhCIBerpe1pAsRW6P4wYc8WHqMdfdr457hd3oOLjhAFx/",
        "checkoutSessionId": "cs_test_b1sNi7D6u9iMCUFV1UZi9ZwerfaiXKdOOmr3DCUW6XdCZIr5Id1F7G"
    }
  ```
</Accordion>

**Required Parameters:**

* `planIdentifier` : The identifier of the specific plan the customer is checking out with. planIdentifier is mandatory.

* `successUrl` : The URL to which the user will be redirected after a successful checkout.

* `chargePeriod` :  The billing cycle for the subscription. Must be one of:

  * `"ONE_TIME"`

  * `"MONTHLY"`

  * `"YEARLY"`

  * `"WEEKLY"`

  * `"DAILY"`

  * `"THREE_MONTHS"`

  * `"SIX_MONTHS"`

**Optional Parameters:**

* `offeringId` : The ID (uuid) of the offering the customer is checking out with.

* `pricingTableId` :  The id (uuid) of the pricing table being used for this checkout. (Considered only if `offeringId` is not provided)

* `ruleId` :  The id (uuid) of the pricing rule being applied. (Considered only if `offeringId` is not provided)

* `customerId` :  The ID of the customer initiating the checkout. (If not provided, a new customer will be created)

* `features`: A list of objects, where each object represents a feature and its desired quantity. Each object **must** have two keys:
  * `"identifier"` : The unique identifier for the feature.
  * `"quantity"`: The desired quantity for this feature.
    * Example: `[{"identifier": "seats", "quantity": 10}, {"identifier": "api-calls-tier1", "quantity": 5000}]`

* `ipAddress` : The IP Address of the customer, used for location based pricing.

* `currencyCode` : The currency code for the checkout session (e.g. `"USD"`). Required when `customAmount` is provided.

* `customAmount` : A custom amount to charge for this checkout session. When provided, `taxBehavior` and `currencyCode` are also required.

* `taxBehavior` : Specifies how taxes are applied to the `customAmount`. Required when `customAmount` is provided. Must be one of:
  * `"INCLUSIVE"` — Tax is included in the custom amount.
  * `"EXCLUSIVE"` — Tax is added on top of the custom amount.

* `discountsEnabled` : Whether the discount/coupon code field is shown on the checkout page. Defaults to `true`.

* `lockEmail` : When `true`, the email field is pre-filled and locked so the customer cannot change it. Defaults to `false`.

* `defaultBillingCountry` : ISO 3166-1 alpha-2 country code (e.g. `"US"`, `"GB"`) used to pre-fill the billing address country on the checkout page.

* `metadata` : An object of arbitrary key-value pairs to attach to the checkout session. The keys are preserved exactly as supplied and the object is returned unchanged on the `checkout.completed` webhook's `metadata` field. Example: `{ "order_ref": "ABC-123", "source": "pricing_page" }`.

**Returns**:

An instance of CreateCheckoutSessionResponse (Object), which includes:

* `checkoutSessionId` : The unique ID for the created checkout session.

* `checkoutUrl` : The URL that the customer should be redirected to in order to complete the payment and activate the subscription/purchase.

***

## Entitlements

The entitlements module allows you to check and retrieve customer entitlements for various features. These operations target a specific edge API endpoint ( `https://edge.api.kelviq.com` by default) and use the GET HTTP method with query parameters.

### Checks if a specific customer has access to a particular feature.

This method directly returns a boolean indicating access status.

```typescript theme={null}
const hasAccess = await client.entitlements.hasAccess({
      customerId: "cust_node_ent_123",
      featureId: "premium-reporting-node"
    });
```

<Accordion title="Response">
  ```shell theme={null}
   true
  ```
</Accordion>

**Required Parameters**:

* `customerId` : The unique identifier for the customer.

* `featureId` : The unique identifier for the feature whose access is being checked.

**Returns**:

* `boolean`: `true` if the customer has access to the specified feature (considering feature type, limits, etc.), `false` otherwise or if the feature is not found in their entitlements.

### Retrieves the aggregated entitlement for a specific feature for a given customer.

```typescript theme={null}
const entitlement = await client.entitlements.getEntitlement({
    customerId: "cust_node_ent_123",
    featureId: "premium-reporting-node"
});
```

<Accordion title="Response">
  ```json theme={null}
   {
        "featureId": "advanced-analytics",
        "featureType": "METER",
        "hasAccess": true,
        "hardLimit": false,
        "usageLimit": 3,
        "currentUsage": 0,
        "remaining": 3,
        "items": [
            {
                "featureId": "advanced-analytics",
                "featureType": "METER",
                "hasAccess": true,
                "resetAt": "2025-05-22 08:27:45",
                "hardLimit": false,
                "usageLimit": 3,
                "currentUsage": 0,
                "remaining": 3
            }
        ]
   }
  ```
</Accordion>

**Required Parameters**:

* `customerId` : The unique identifier for the customer.

* `featureId` : The unique identifier for the feature whose access is being checked.

**Returns**:

An aggregated `Entitlement` object, or `null` if the feature is not found. If the customer has multiple subscriptions, there can be multiple raw entries for the same featureId. The SDK aggregates them and keeps raw entries in the `items` array. Note: `resetAt` is only available on individual items (not at the top level) because it can differ across subscriptions. The structure includes:

* `featureId`: string

* `hasAccess`: boolean — For **METER** features, computed as `remaining === null || remaining > 0`. For **BOOLEAN** and **CUSTOMIZABLE** features, `true` if at least one item grants access.

* `featureType`: string — `"METER"`, `"BOOLEAN"`, or `"CUSTOMIZABLE"`.

* `hardLimit`: boolean | null — For **METER**, `true` if any item has a hard limit. For **BOOLEAN**, always `false`.

* `usageLimit`: number | null — For **METER**, summed across all items. For **CUSTOMIZABLE**, taken from the first item. For **BOOLEAN**, always `null`.

* `currentUsage`: number | null — For **METER**, summed across all items. For **CUSTOMIZABLE**, taken from the first item. For **BOOLEAN**, always `0`.

* `remaining`: number | null — For **METER**, computed as `usageLimit - currentUsage`. For **CUSTOMIZABLE**, taken from the first item. For **BOOLEAN**, always `null`.

* `items` (`EntitlementDetail[]`): An array containing all raw entitlement entries for this featureId. Each item includes `resetAt`.

### Retrieves all aggregated entitlements for a given customer.

```typescript theme={null}
const entitlements = await client.entitlements.getEntitlements({
    customerId: "cust_node_ent_123"
});

// Access a specific feature directly by featureId
const analytics = entitlements["advanced-analytics"];
```

<Accordion title="Response">
  ```json theme={null}
   {
        "advanced-analytics": {
            "featureId": "advanced-analytics",
            "featureType": "METER",
            "hasAccess": true,
            "hardLimit": false,
            "usageLimit": 3,
            "currentUsage": 0,
            "remaining": 3,
            "items": [
                {
                    "featureId": "advanced-analytics",
                    "featureType": "METER",
                    "hasAccess": true,
                    "resetAt": "2025-05-22 08:27:45",
                    "hardLimit": false,
                    "usageLimit": 3,
                    "currentUsage": 0,
                    "remaining": 3
                }
            ]
        }
   }
  ```
</Accordion>

**Required Parameters**:

* `customerId` : The unique identifier for the customer.

**Returns**:

A `Record<string, Entitlement>` keyed by `featureId`. If the customer has multiple subscriptions, there can be multiple raw entries for the same featureId. The SDK aggregates them and keeps raw entries in the `items` array. Note: `resetAt` is only available on individual items (not at the top level) because it can differ across subscriptions. Each `Entitlement` value has the same structure as described in `getEntitlement` above.

### Retrieves the raw entitlement from the API for a specific feature.

```typescript theme={null}
const response = await client.entitlements.getRawEntitlement({
    customerId: "cust_node_ent_123",
    featureId: "premium-reporting-node"
});
```

<Accordion title="Response">
  ```json theme={null}
   {
        "customerId": "cust_456",
        "entitlements": [
            {
                "featureId": "advanced-analytics",
                "featureType": "METER",
                "hasAccess": true,
                "resetAt": "2025-05-22 08:27:45",
                "hardLimit": false,
                "usageLimit": 3,
                "currentUsage": 0,
                "remaining": 3
            }
        ]
   }
  ```
</Accordion>

**Required Parameters**:

* `customerId` : The unique identifier for the customer.

* `featureId` : The unique identifier for the feature.

**Returns**:

An instance of `CheckEntitlementsResponse` (Object). This returns the raw API response without aggregation. The structure includes:

* `customerId` : The customer's ID.

* `entitlements` (`EntitlementDetail[]`): A list containing raw entitlement details. Each `EntitlementDetail` has fields like:

  * `featureId`: string

  * `hasAccess`: boolean

  * `featureType`: string

  * `resetAt`: string

  * `hardLimit`: boolean | null

  * `usageLimit`: number | null

  * `currentUsage`: number | null

  * `remaining`: number | null

### Retrieves all raw entitlements from the API without aggregation.

```typescript theme={null}
const response = await client.entitlements.getRawEntitlements({
    customerId: "cust_node_ent_123"
});
```

<Accordion title="Response">
  ```json theme={null}
   {
        "customerId": "cust_456",
        "entitlements": [
            {
                "featureId": "advanced-analytics",
                "featureType": "METER",
                "hasAccess": true,
                "resetAt": "2025-05-22 08:27:45",
                "hardLimit": false,
                "usageLimit": 3,
                "currentUsage": 0,
                "remaining": 3
            }
        ]
   }
  ```
</Accordion>

**Required Parameters**:

* `customerId` : The unique identifier for the customer.

**Returns**:

An instance of `CheckEntitlementsResponse` (Object). This returns the raw API response without aggregation. The structure includes:

* `customerId` : The customer's ID.

* `entitlements` (`EntitlementDetail[]`): A list containing raw entitlement details. Each `EntitlementDetail` has fields like:

  * `featureId`: string

  * `hasAccess`: boolean

  * `featureType`: string

  * `resetAt`: string

  * `hardLimit`: boolean | null

  * `usageLimit`: number | null

  * `currentUsage`: number | null

  * `remaining`: number | null

***

## Reporting

### Reporting pre-aggregated usages for customer

This endpoint is used for reporting the pre-aggregated feature usage from your application (client-level) to the Kelviq application. It allows you to update the usage count for a specific feature associated with a customer.

```typescript theme={null}
import { BEHAVIOUR_CHOICES } from '@kelviq/node-sdk';

const response = await client.reporting.reportUsage({
    value: 150,
    customerId: "customer_001",
    featureId: "seats",
    behaviour: BEHAVIOUR_CHOICES.SET
});
```

<Accordion title="Response">
  ```json theme={null}
   {
        "value": 150,
        "customerId": "customer_001",
        "featureId": "seats",
        "behaviour": "SET",
        "orgId": "1",
        "eventName": "aggregated.usage",
        "idempotencyKey": "597ee95063c744ed9bcc9b1cf5676a8a",
        "timestamp": "2025-05-22 08:27:45.430732"
    }
  ```
</Accordion>

**Required Parameters**:

* `value`: The usage value being reported.

* `customerId` : The unique identifier for the customer associated with this usage.

* `featureId` : The unique identifier for the feature for which usage is being reported.

* `behaviour` parameter dictates how the usage is updated:

  * `SET`: This will replace the current usage value for the feature with the new `value` provided.

  * `DELTA`: This will increment the existing usage value for the feature by the amount specified in the `value` parameter

**Returns**:

An instance of ReportUsageResponse (Object), which includes:

* `value`: The usage value that was recorded.

* `customerId` : The customer ID associated with the usage.

* `featureId` : The feature Identifier for which usage was recorded.

* `behaviour` : The behaviour type ("SET" or "DELTA") that was processed.

* `orgId` : The organization ID associated with this record, as determined by the server.

* `eventName` : An internal event name generated by the server for this usage report (e.g., "aggregated.usage").

* `idempotencyKey` : A unique idempotency key generated by the server for this specific usage report instance.

* `timestamp` : The server-generated UTC timestamp (string format) indicating when the usage report was processed.

{/* Raw events are primarily used for metered billing scenarios, particularly when a customer is subscribed to a plan with usage-based billing (often referred to as "pay as you go"). Each event reported can contribute to the billable usage for that customer. */}

{/* import { v4 as uuidv4 } from 'uuid'; */}

{/* const response = await client.reporting.reportEvent({ */}

{/*     eventName: "api_call_node", */}

{/*     timestamp: timestampStr, */}

{/*         "featureId": "api-usage", */}

{/*     } */}

{/* ``` */}

{/*   ```json */}

{/*         "customerId": "customer_002", */}

{/*         "idempotencyKey": "45f05c737a0b44d482c6042816d5645d", */}

{/*         "properties": { */}

{/*             "value": 2 */}

{/*         "orgId": "1" */}

{/*   ``` */}

{/* **Required Parameters**: */}

{/*     * `eventName` : The name of the event (e.g., "user_login", "item_purchased", "feature_activated"). */}

{/*     * `timestamp` : The UTC timestamp indicating when the event occurred. This must be a string formatted as "%Y-%m-%d %H:%M:%S.%f" (e.g., "2025-05-23 10:30:00.123456"). */}

{/* **Returns**: */}

{/*     * `customerId` : The customer ID associated with the event. */}

{/*     * `idempotencyKey` : The idempotency key that was used for the request. */}

{/*     * `properties` : The custom properties associated with the event, if provided and returned by the server. */}

{/* --- */}

## Subscriptions

The subscriptions module allows you to list, retrieve, create, update, and cancel customer subscriptions. You can access these operations via the `subscriptions` attribute on an initialized Kelviq client instance.

### List subscriptions

Retrieves a paginated list of subscriptions for a customer.

```typescript theme={null}
const page = await client.subscriptions.list({
  customerId: "cust_789",
  page: 1,
  pageSize: 20,
});

for (const subscription of page.results) {
  console.log(subscription.id, subscription.status);
}
```

<Accordion title="Response">
  ```json theme={null}
  {
    "count": 1,
    "next": null,
    "previous": null,
    "results": [
      {
        "id": "09d706ca-58f3-4fb2-818f-5b8623d67e6e",
        "startDate": "2025-07-28",
        "endDate": null,
        "status": "active",
        "trialDaysRemaining": 0,
        "customerId": "cust_789",
        "billingType": "SUBSCRIPTION",
        "files": [],
        "links": [],
        "license": []
      }
    ]
  }
  ```
</Accordion>

**Required Parameters:**

* `customerId` (string): The client-defined identifier of the customer whose subscriptions will be returned.

**Optional Parameters:**

* `page` (number): Page number to retrieve.
* `pageSize` (number): Number of subscriptions to return per page.

**Returns:**

A `PaginatedSubscriptionResponse` containing `count`, `next`, `previous`, and a `results` array of `SubscriptionData` objects.

### Retrieve a subscription

Retrieves one subscription using its Kelviq subscription ID.

```typescript theme={null}
const subscription = await client.subscriptions.retrieve({
  subscriptionId: "09d706ca-58f3-4fb2-818f-5b8623d67e6e",
});

console.log(subscription.id, subscription.status);
subscription.files?.forEach((file) => console.log(file.name, file.downloadUrl));
subscription.links?.forEach((link) => console.log(link.name, link.url));
subscription.license?.forEach((license) =>
  console.log(license.licenseKey, license.activationLimit),
);
```

<Accordion title="Response">
  ```json theme={null}
  {
    "id": "09d706ca-58f3-4fb2-818f-5b8623d67e6e",
    "externalSubscriptionId": "sub_1RpoZoSBstCzyocS7iPmith3",
    "startDate": "2025-07-28",
    "endDate": null,
    "billingPeriodStartTime": "2025-07-28T10:46:34Z",
    "billingPeriodEndTime": "2025-08-28T10:46:34Z",
    "amount": "261.00",
    "recurrence": "month",
    "currency": "usd",
    "status": "active",
    "trialDaysRemaining": 0,
    "customerId": "cust_789",
    "billingType": "SUBSCRIPTION",
    "recurrenceUnit": 1,
    "recurrenceType": "MONTH",
    "files": [],
    "links": [],
    "license": []
  }
  ```
</Accordion>

**Required Parameters:**

* `subscriptionId` (string): The Kelviq UUID of the subscription.

**Returns:**

A `SubscriptionData` object. A missing subscription throws `NotFoundError`.

### Create a subscription

Creates a subscription directly for a customer without requiring a checkout session.

```typescript theme={null}
import { CHARGE_PERIOD_CHOICES } from '@kelviq/node-sdk';

const subscription = await client.subscriptions.create({
  planIdentifier: "plan-pro-monthly",
  chargePeriod: CHARGE_PERIOD_CHOICES.MONTHLY,
  customerId: "cust_789",
  successUrl: "https://example.com/subscription/success",
  features: [{ identifier: "seats", quantity: 5 }],
  ipAddress: "103.154.35.20",
});

console.log(subscription.id, subscription.status);
```

**Required Parameters:**

* `planIdentifier` (string): Identifier of the plan to subscribe to.
* `chargePeriod` (`ChargePeriod`): Billing period such as `MONTHLY`, `YEARLY`, `WEEKLY`, or `DAILY`.
* `customerId` (string): Client-defined identifier of the customer.

**Optional Parameters:**

* `successUrl` (string): URL to redirect to after successful subscription creation.
* `features` (`FeatureListItem[]`): Feature identifiers and requested quantities.
* `ipAddress` (string): Customer IP address used for location-based pricing.

**Returns:**

A `CreateSubscriptionResponse` containing the created subscription, including its plan files, links, and issued licenses when available.

### Updates an existing subscription to a new plan

```typescript theme={null}
import { CHARGE_PERIOD_CHOICES, PAYMENT_BEHAVIORS } from '@kelviq/node-sdk';

try {
    const response = await client.subscriptions.update({
      subscriptionId: "sub_node_78058918",
      planIdentifier: "premium-plan-node",
      chargePeriod: CHARGE_PERIOD_CHOICES.MONTHLY,
      trialEnd: "2027-12-31T23:59:59Z", // or "now" to end the trial immediately
      paymentBehavior: PAYMENT_BEHAVIORS.ACTIVATE_ON_PAYMENT
    });
} catch (e) {
    console.error(`Error updating subscription: ${e}`);
}
```

<Accordion title="Response">
  ```json theme={null}
   {
      "subscriptionId": "dffaf07e-4517-47db-ba3a-59a05aa2d465"
   }
  ```
</Accordion>

**Required Parameters**:

* `subscriptionId` :  The unique identifier of the subscription to be updated.
* `planIdentifier` :  The identifier of the new plan.
* `chargePeriod` :  The new charging period for the subscription. Must be one of:
  * `"ONE_TIME"`
  * `"MONTHLY"`
  * `"YEARLY"`
  * `"WEEKLY"`
  * `"DAILY"`
  * `"THREE_MONTHS"`
  * `"SIX_MONTHS"`

**Optional Parameters:**

* `offeringId` :  The ID of the new offering, if applicable.
* `pricingTableId` :  The ID of the new pricing table, if applicable.
* `ruleId` :  The ID of the new pricing rule, if applicable.
* `ipAddress` : The IP Address of the customer, used for location based pricing.
* `features`:  An array of objects, where each object represents a feature and its desired quantity to update for the subscription. Each object **must** have two keys:
  * `"identifier"` : The unique identifier for the feature.
  * `"quantity"` : The desired quantity for this feature.
    * Example: `[{"identifier": "seats", "quantity": 10}, {"identifier": "projects", "quantity": 5}]`
* `trialEnd` (string): Controls the trial period for the updated subscription. Accepts either:
  * The literal string `"now"` to end any active trial immediately.
  * An ISO 8601 datetime string (e.g. `"2027-12-31T23:59:59Z"`) to set a new trial end date. The datetime must be in the future. Naive datetimes (no timezone designator) are interpreted as UTC.
  * If omitted, the existing trial behavior on the subscription is preserved.
* `paymentBehavior` (`PaymentBehavior`): Set to `PAYMENT_BEHAVIORS.ACTIVATE_ON_PAYMENT` (the value `"activate_on_payment"`) to keep the current subscription active while payment is pending. The new plan and features are applied only after payment succeeds. If the pending update expires, the current subscription remains unchanged. Omit this field to use the default immediate-update behavior.

The SDK validates `trialEnd` client-side — invalid datetime strings or past datetimes throw `InvalidRequestError` before the request is sent.

**Returns**:

An instance of `UpdateSubscriptionResponse`, which includes:

* `subscriptionId` (string): UUID of the updated subscription.

### Cancel an active subscription for a customer.

```typescript theme={null}
import { CANCELLATION_TYPES } from '@kelviq/node-sdk';

try {
    const response = await client.subscriptions.cancel({
        subscriptionId: "sub_node_78058918",
        cancellationType: CANCELLATION_TYPES.CURRENT_PERIOD_ENDS
    });
} catch (e) {
    console.error(`Error cancelling subscription: ${e}`);
}
```

<Accordion title="Response">
  ```json theme={null}
   {
      "message": "Subscription cancellation processed successfully."
   }
  ```
</Accordion>

**Required Parameters**:

* `subscriptionId` : The unique identifier of the subscription to be cancelled.

* `cancellationType` : The type of cancellation to perform. Must be one of:

  * `"IMMEDIATE"`: The subscription is cancelled immediately.
  * `"CURRENT_PERIOD_ENDS"`: The subscription will remain active until the end of the current billing period and then cancel.
  * `"SPECIFIC_DATE"`: The subscription will be cancelled on the specified `cancellationDate`.

* `cancellationDate` : The specific date for cancellation if `cancellationType` is `"SPECIFIC_DATE"`. Must be in `YYYY-MM-DD` format. This parameter is **required** if `cancellationType` is `"SPECIFIC_DATE"`.

**Returns**:

An instance of `CancelSubscriptionResponse`, which includes:

* `message` : A confirmation message indicating the result of the cancellation request.

***

## License

The `license` module allows you to activate, deactivate, and validate software licenses. You can access these operations via the `license` attribute on an initialized `Kelviq` client instance.

***

### Activate a license key

Creates a new license instance for a given license key, optionally associating it with a customer.

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

<Accordion title="Response">
  ```json theme={null}
  {
    "instanceId": "8f3e2b1a-5c6d-4e9f-8a0b-1c2d3e4f5g6h",
    "activatedAt": "2026-01-27T10:33:00Z",
    "expiresOn": "2027-01-27T10:33:00Z",
    "license": {
      "id": "7be7a5b6-7a0e-4178-a085-8ef48f0649ab",
      "licenseKey": "LIC-A8D5F89E-B46B-472C-9BED-66EE2545B85E",
      "activatedOn": "2026-01-22T03:26:56Z",
      "expiresOn": "2026-01-23T03:26:56Z",
      "activationUsage": 1,
      "activationLimit": 4,
      "enabled": true,
      "customer": {
        "customerId": "cfa95cf8c8d24e8b9d3d2d1ada98a94d",
        "name": "Geo",
        "email": "geo@kelviq.com"
      },
      "plan": {
        "identifier": "pro-yearly",
        "name": "Pro Yearly",
        "description": "Pro plan billed yearly",
        "product": {
          "id": "d970ab2d-834f-4c52-8742-2e6ed7e8a31f",
          "identifier": "kelviq-engine",
          "name": "Kelviq Engine",
          "taxCode": "software",
          "createdOn": "2025-01-01T00:00:00Z",
          "modifiedOn": "2025-06-01T00:00:00Z"
        },
        "version": 1,
        "isLatest": true
      },
      "subscription": {
        "id": "09d706ca-58f3-4fb2-818f-5b8623d67e6e",
        "recurrence": "1 month",
        "billingPeriodStartTime": "2026-01-22T03:26:56Z",
        "billingPeriodEndTime": "2026-02-22T03:26:56Z",
        "startDate": "2026-01-22",
        "endDate": null,
        "status": "active",
        "amount": "49.00",
        "currency": "usd",
        "trialDaysRemaining": 0,
        "billingType": "SUBSCRIPTION",
        "recurrenceUnit": 1,
        "recurrenceType": "MONTH"
      }
    }
  }
  ```
</Accordion>

**Required Parameters:**

* `licenseKey` : The license key string to activate.

**Optional Parameters:**

* `customerId` : The ID of the customer this instance is associated with.
* `instanceName` : A human-readable name for this instance (e.g. device name).
* `metadata` : An object of custom key-value pairs to attach to the instance.

**Returns:**

An instance of `LicenseActivateResponse`, which includes:

* `instanceId` (string): The unique ID of the newly created license instance.
* `activatedAt` (string): ISO 8601 timestamp of when the instance was activated.
* `expiresOn` (string | null): ISO 8601 timestamp of when the instance expires, if applicable.
* `license` (`LicenseDetails`): The full license object. Key fields:
  * `id` : Server-generated UUID for the license.
  * `licenseKey` : The license key string.
  * `activatedOn` (string | null): ISO 8601 timestamp of when the license was first activated.
  * `expiresOn` (string | null): ISO 8601 timestamp of when the license expires.
  * `activationUsage` : Number of currently active instances.
  * `activationLimit` : Maximum number of allowed concurrent activations.
  * `enabled` : Whether the license is active.
  * `customer` (object | null): The associated customer, if any. Fields: `customerId`, `name`, `email`.
  * `plan` (object | null): The plan associated with this license, if any. Fields: `identifier`, `name`, `description`, `version`, `isLatest`, and `product` (object with `id`, `identifier`, `name`, `taxCode`, `createdOn`, `modifiedOn`).
  * `subscription` (object | null): Subscription details, if any. Fields: `id`, `recurrence`, `billingPeriodStartTime`, `billingPeriodEndTime`, `startDate`, `endDate`, `status`, `amount`, `currency`, `trialDaysRemaining`, `billingType`, `recurrenceUnit`, `recurrenceType`.

***

### Deactivate a license instance

Deactivates a specific license instance by its instance ID.

```typescript theme={null}
try {
    const response = await client.license.deactivate({
        licenseKey: "LIC-XXXX-YYYY-ZZZZ",
        instanceId: "8f3e2b1a-5c6d-4e9f-8a0b-1c2d3e4f5a6b"
    });
    console.log(response.message);
} catch (e) {
    console.error(`Error deactivating license: ${e}`);
}
```

<Accordion title="Response">
  ```json theme={null}
  {
    "message": "License instance deactivated successfully.",
    "deactivatedAt": "2026-01-27T10:35:00Z"
  }
  ```
</Accordion>

**Required Parameters:**

* `licenseKey` : The license key string.
* `instanceId` : The unique ID of the instance to deactivate.

**Returns:**

An instance of `LicenseDeactivateResponse`, which includes:

* `message` (string): Confirmation message.
* `deactivatedAt` (string): ISO 8601 timestamp of when the instance was deactivated.

***

### Validate a license key

Checks whether a license key (and optionally a specific instance) is valid.

```typescript theme={null}
const response = await client.license.validate({
    licenseKey: "LIC-XXXX-YYYY-ZZZZ",
    instanceId: "8f3e2b1a-5c6d-4e9f-8a0b-1c2d3e4f5a6b"  // optional
});

if (response.valid) {
    console.log("License is valid:", response.code);
} else {
    console.log("License invalid:", response.detail);
}
```

<Accordion title="Response">
  ```json theme={null}
  {
    "valid": true,
    "code": "VALID",
    "detail": "Instance ID valid for this license.",
    "metadata": {
      "os": "macOS",
      "arch": "arm64"
    },
    "license": {
      "id": "7be7a5b6-7a0e-4178-a085-8ef48f0649ab",
      "licenseKey": "LIC-A8D5F89E-B46B-472C-9BED-66EE2545B85E",
      "activatedOn": "2026-01-22T03:26:56Z",
      "expiresOn": "2026-01-23T03:26:56Z",
      "activationUsage": 1,
      "activationLimit": 4,
      "enabled": true,
      "customer": {
        "customerId": "cfa95cf8c8d24e8b9d3d2d1ada98a94d",
        "name": "Geo",
        "email": "geo@kelviq.com"
      },
      "plan": {
        "identifier": "pro-yearly",
        "name": "Pro Yearly",
        "product": {
          "id": "d970ab2d-834f-4c52-8742-2e6ed7e8a31f",
          "identifier": "kelviq-engine",
          "name": "Kelviq Engine",
          "taxCode": "software"
        }
      },
      "subscription": {
        "id": "09d706ca-58f3-4fb2-818f-5b8623d67e6e",
        "recurrence": "1 month",
        "billingPeriodStartTime": "2026-01-22T03:26:56Z",
        "billingPeriodEndTime": "2026-02-22T03:26:56Z",
        "startDate": "2026-01-22",
        "endDate": null,
        "status": "active",
        "amount": "49.00",
        "currency": "usd",
        "trialDaysRemaining": 0,
        "billingType": "SUBSCRIPTION",
        "recurrenceUnit": 1,
        "recurrenceType": "MONTH"
      }
    }
  }
  ```
</Accordion>

**Required Parameters:**

* `licenseKey` : The license key string to validate.

**Optional Parameters:**

* `instanceId` : If provided, also validates that this specific instance is active for the given license key.

**Returns:**

An instance of `LicenseValidateResponse`, which includes:

* `valid` (boolean): Whether the license (and instance, if provided) is valid.
* `code` (string): A short status code (e.g. `"VALID"`, `"INVALID"`, `"EXPIRED"`).
* `detail` (string): A human-readable description of the validation result.
* `metadata` (object | null): Any metadata attached to the instance, if applicable.
* `license` (`LicenseDetails | null`): The full license object (same structure as in `activate`), or `null` if not found.

***

## Portal

The portal module allows you to create authenticated customer portal sessions. Once a session is created, you can redirect your customer directly to their self-serve customer portal without requiring them to log in manually. You can access these operations via the `portal` attribute on an initialized `Kelviq` client instance.

### Creates a new customer portal session

```typescript theme={null}
const session = await client.portal.createSession({
    customerId: "cust_789",
});

// Build the signed portal link — customerPortalUrl alone does not
// authenticate; the token must be appended as a query parameter.
const portalLink = `${session.customerPortalUrl}?token=${session.token}`;

// Redirect your customer to the portal
res.redirect(portalLink);
```

<Accordion title="Response">
  ```json theme={null}
  {
    "token": "cpt-4f7c5dc4-da9f-447b-d447-6206d835ed6d:23fca530-ajeb-4685-b3d5-9918h6d38275",
    "email": "geo@example.com",
    "customerPortalUrl": "https://www.kelviq.com/portal/paritydeals/"
  }
  ```
</Accordion>

**Required Parameters:**

* `customerId` : The unique identifier of the customer for whom the portal session is being created.

**Returns:**

An instance of `CreatePortalSessionResponse` (TypeScript Interface), which includes:

* `token` : The session token that authenticates the customer portal session.
* `email` : The email address of the customer.
* `customerPortalUrl` : The customer's billing portal base URL. **This URL alone does not authenticate the session** — append the `token` as a query parameter (`${customerPortalUrl}?token=${token}`) to produce the signed link you share with the customer. The token expires, so create a fresh session per visit.

***

## Webhooks

Kelviq can send real-time webhook notifications to your server when events occur in your account — for example, when a subscription is created or an invoice is paid.

### Verifying webhook signatures

Every webhook request includes three headers that you must use to verify the request is genuinely from Kelviq:

| Header              | Description                                |
| ------------------- | ------------------------------------------ |
| `webhook-id`        | Unique identifier for the event            |
| `webhook-timestamp` | Unix timestamp of when the event was sent  |
| `webhook-signature` | HMAC-SHA256 signature, prefixed with `v1,` |

Use the `validateEvent` helper to verify the signature and parse the event in one step. It throws `WebhookVerificationError` if the signature is invalid.

```typescript theme={null}
import { validateEvent, WebhookVerificationError } from '@kelviq/node-sdk';
import express from 'express';

const app = express();

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  try {
    const event = validateEvent(
      req.body,
      req.headers,
      '<YOUR_WEBHOOK_SECRET>',
    );

    // Process the event
    console.log('Event type:', event.type);

    res.sendStatus(202);
  } catch (err) {
    if (err instanceof WebhookVerificationError) {
      return res.sendStatus(403);
    }
    throw err;
  }
});
```

<Warning>
  Use `express.raw()` (not `express.json()`) so that the raw request body is preserved for signature verification. Parsing the body as JSON before verification will break the signature check.
</Warning>

### Webhook event types

| Event                       | Description                       |
| --------------------------- | --------------------------------- |
| `checkout.completed`        | A checkout session was completed  |
| `customer.created`          | A new customer was created        |
| `customer.updated`          | A customer's details were updated |
| `order.created`             | A new order was placed            |
| `order.updated`             | An order was updated              |
| `order.refunded`            | An order was refunded             |
| `subscription.created`      | A new subscription was created    |
| `subscription.updated`      | A subscription was updated        |
| `subscription.cancelled`    | A subscription was cancelled      |
| `subscription.plan_changed` | A subscription plan was changed   |
| `invoice.created`           | A new invoice was created         |
| `invoice.paid`              | An invoice was paid               |
| `refund.created`            | A refund was initiated            |
| `refund.updated`            | A refund status changed           |

### Best practices

* **Return quickly** — respond with a `2xx` status before doing any heavy processing. Kelviq retries up to 3 times at 60-second intervals if it does not receive a `2xx`.
* **Use idempotency** — use the `webhook-id` header (or the event `id` field) to detect and skip duplicate deliveries.
* **Validate every request** — always verify the signature before trusting the payload.

***

## Using the Sandbox Environment

For testing and development, you can configure the client to use the sandbox environment. This ensures that no production data is affected. To do this, set the environment option to 'sandbox' during client initialization.

```typescript theme={null}
import { Kelviq } from '@kelviq/node-sdk';

const client = new Kelviq({
  accessToken: 'YOUR_SANDBOX_ACCESS_TOKEN',
  environment: 'sandbox'
});

```

<Info>
  The sandbox environment is completely separate from production. You will need to use a different set of API keys, and any data created (customers, subscriptions, etc.) will only exist in the sandbox.
</Info>
