> ## 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.

# JS SDK

> Changelog for @kelviq/js-sdk

<Update label="v2.1.0" description="May 9, 2026">
  ### New Features

  #### Product Offerings — Pricing API Support

  The SDK can now fetch and display product pricing data. Pass a `productId` to the client to enable pricing methods.

  ```ts theme={null}
  const client = kelviqSDK({
    productId: 'your-product-id',
    accessToken: 'your-access-token',
  });
  ```

  If `customerId` is also set, it is forwarded to the pricing API so the response reflects that customer's subscription state.

  ***

  ##### `fetchPricing(forceRefresh?)` — Fetch and Cache Pricing

  Fetches product offering data for the configured `productId`. Subsequent calls return the cached result unless `forceRefresh` is `true`.

  ```ts theme={null}
  const pricing = await client.fetchPricing();
  console.log(pricing.plans, pricing.currencyCode, pricing.billingPeriods);
   
  await client.fetchPricing(true); // bypass cache
  ```

  ##### `getPricing()` — Read from Cache

  Returns cached pricing data synchronously, or `null` if not yet fetched.

  ```ts theme={null}
  const pricing = client.getPricing();
  ```

  ##### `getPlan(identifier)` — Look Up a Single Plan

  Finds an enabled plan by identifier from the pricing cache. Returns `null` if not found or pricing hasn't been fetched.

  ```ts theme={null}
  const plan = client.getPlan('pro-plan');
  console.log(plan?.displayName, plan?.price.priceType);
  ```

  ##### `isPricingLoading()` — Loading State

  Returns `true` while a pricing fetch is in progress.

  ```ts theme={null}
  if (client.isPricingLoading()) { /* show spinner */ }
  ```

  ##### `getLastPricingError()` — Error State

  Returns the last `Error` from a failed pricing fetch, or `null`.

  ```ts theme={null}
  const err = client.getLastPricingError();
  ```

  ##### `clearPricingCache()` — Reset Cache

  Clears cached pricing data and resets loading and error states.

  ```ts theme={null}
  client.clearPricingCache();
  ```

  ***

  ##### `renderPricing(options?)` — DOM Binding

  Scans the DOM for elements with `data-kq-price` attributes and populates them with localized formatted prices. Requires pricing data to be fetched first.

  ```html theme={null}
  <span data-kq-price="pro-plan" data-kq-period="MONTHLY">Loading...</span>
  ```

  ```ts theme={null}
  await client.fetchPricing();
  client.renderPricing();
   
  // Scope to a container
  client.renderPricing({ container: document.getElementById('pricing-section') });
   
  // With format options
  client.renderPricing({ formatOptions: { compact: true } });
  ```

  | Attribute        | Required | Description                                                                          |
  | ---------------- | -------- | ------------------------------------------------------------------------------------ |
  | `data-kq-price`  | Yes      | Plan identifier                                                                      |
  | `data-kq-period` | No       | Billing period (e.g. `MONTHLY`, `YEARLY`). Falls back to the first available charge. |

  ***

  ##### `kqFormatPrice(amount, currencySymbol, options?)` — Price Formatter

  Standalone named export for formatting a numeric amount with a currency symbol.

  ```ts theme={null}
  import { kqFormatPrice } from '@kelviq/js-sdk';
   
  kqFormatPrice(9.99, '$', { pricingLocale: 'en-US' });                  // "$9.99"
  kqFormatPrice(1200, '€', { compact: true, pricingLocale: 'de-DE' });   // "€1,2K"
  kqFormatPrice(9.99, '$', { includeCurrencySymbol: false });             // "9.99"
  ```

  | Option                  | Type      | Default         | Description                           |
  | ----------------------- | --------- | --------------- | ------------------------------------- |
  | `compact`               | `boolean` | `false`         | Use compact notation (e.g. `1.2K`)    |
  | `locale`                | `string`  | `pricingLocale` | Override locale for number formatting |
  | `pricingLocale`         | `string`  | `'en-US'`       | Locale from the pricing API           |
  | `includeCurrencySymbol` | `boolean` | `true`          | Prepend the currency symbol           |

  ***

  ##### New Types and Exports

  ```ts theme={null}
  export { kqFormatPrice } from './formatPrice';
  export type { KQFormatPriceOptions } from './formatPrice';
  export type {
    RawPricingFeature,
    RawPricingCharge,
    RawPricingPrice,
    RawPricingPlan,
    RawPricingBillingPeriod,
    RawPricingApiResponse,
  } from './types';
  ```
</Update>

<Update label="v2.0.0" description="February 27, 2026">
  ### New Features

  #### Hybrid Aggregation Engine

  If a customer has multiple subscriptions (e.g., a base plan + a top-up), the API may return duplicate `featureId` entries. The SDK now automatically aggregates them into a single `Entitlement` object per feature — no manual merging required.

  **Aggregation rules:**

  | Feature Type     | `hasAccess`                            | `currentUsage`     | `usageLimit`       | `hardLimit`                |
  | ---------------- | -------------------------------------- | ------------------ | ------------------ | -------------------------- |
  | **METER**        | `true` if `remaining > 0` or unlimited | Summed             | Summed             | `true` if any item sets it |
  | **BOOLEAN**      | `true` if any item grants access       | `0`                | `null`             | `false`                    |
  | **CUSTOMIZABLE** | `true` if any item grants access       | First item's value | First item's value | First item's value         |

  Per-item details (like `resetAt` and individual `hardLimit` values) are available on `entitlement.items[]`.

  #### `getEntitlements()` — Fetch All Entitlements

  Returns all aggregated entitlements as a map keyed by `featureId`. Replaces the old `getAllEntitlements()` method.

  ```typescript theme={null}
  const entitlements = kq.getEntitlements();
  // { "email-sends": Entitlement, "api-calls": Entitlement, ... }
  ```

  #### `getRawEntitlement(featureId)` — Raw Data for a Single Feature

  Returns the un-aggregated raw API items for a specific `featureId`. Useful when you need per-subscription details.

  ```typescript theme={null}
  const rawItems = kq.getRawEntitlement("email-sends");
  // [{ featureId: 'email-sends', featureType: 'METER', hasAccess: true, ... }, ...]
  ```

  #### `getRawEntitlements()` — Full Raw API Response

  Returns the complete raw API response including the `customerId` wrapper, before any aggregation.

  ```typescript theme={null}
  const raw = kq.getRawEntitlements();
  // { customerId: 'cust_123', entitlements: [...] }
  ```

  #### `ready()` — Wait for Initial Fetch

  Returns a promise that resolves once the initial entitlement fetch completes. If `initializeAndFetch` was not set, resolves immediately.

  ```typescript theme={null}
  const kq = kelviqSDK({
    customerId: "cust_123",
    accessToken: "your-access-token",
  });

  await kq.ready();
  // Entitlements are now available
  const emails = kq.getEntitlement("email-sends");
  console.log(emails.hasAccess, emails.remaining);
  ```

  #### New Type Exports

  `RawEntitlement` and `RawEntitlementsApiResponse` are now exported for consumers who need to type the raw API objects in their own code.

  ***

  ### Breaking Changes

  #### Unified `Entitlement` Interface

  The separate `BooleanEntitlement`, `ConfigEntitlement`, and `MeteredEntitlement` types have been replaced with a single, unified `Entitlement` interface:

  ```typescript theme={null}
  interface Entitlement {
    featureId: string;
    featureType: "METER" | "BOOLEAN" | "CUSTOMIZABLE";
    hasAccess: boolean;      // true if at least one item grants access
    hardLimit: boolean;      // true if any item has hardLimit set
    currentUsage: number;    // SUM of all items' currentUsage (METER only)
    usageLimit: number | null; // SUM of all items' usageLimit (METER only)
    remaining: number | null;  // usageLimit - currentUsage
    items: RawEntitlement[];   // Original API objects for this featureId
  }
  ```

  **Migration:** Replace all references to `BooleanEntitlement`, `ConfigEntitlement`, `MeteredEntitlement`, and `AnyEntitlement` with `Entitlement`.

  #### `getEntitlement()` No Longer Accepts a Type Argument

  The generic type parameter and second `type` argument have been removed. The SDK now determines the feature type automatically.

  ```typescript theme={null}
  // Before
  const emailQuota = kq.getEntitlement<MeteredEntitlement>("email-sends", "metered");
  console.log(emailQuota.used, emailQuota.limit);

  // After
  const emailQuota = kq.getEntitlement("email-sends");
  console.log(emailQuota.currentUsage, emailQuota.usageLimit);
  ```

  #### Renamed Fields

  | Before          | After          | Notes                                                                    |
  | --------------- | -------------- | ------------------------------------------------------------------------ |
  | `used`          | `currentUsage` | Aligned with the raw API field name                                      |
  | `limit`         | `usageLimit`   | Aligned with the raw API field name                                      |
  | `type`          | `featureType`  | Now uses uppercase API values (`"METER"`, `"BOOLEAN"`, `"CUSTOMIZABLE"`) |
  | `configuration` | *(removed)*    | No longer surfaced at the top level                                      |
  | `resetAt`       | *(removed)*    | Access via `entitlement.items[].resetAt` (may differ per subscription)   |

  #### Removed Exports

  The following types are no longer exported:

  * `BooleanEntitlement`, `ConfigEntitlement`, `MeteredEntitlement`, `AnyEntitlement`
  * `EntitlementMap` is now `Record<string, Entitlement>` (no union type)

  #### `getAllEntitlements()` Removed

  Use `getEntitlements()` instead, which returns the aggregated map.

  ***

  ### Changed

  * **`fetchAllEntitlements()` deduplicates concurrent calls** — Instead of rejecting with "already in progress", it returns the same in-flight promise. This makes `initializeAndFetch: true` safely composable with `await fetchAllEntitlements()`.
  * **`fetchAllEntitlements()` stores raw data** — Now stores both the full raw API response and the aggregated cache internally.
  * **`clearCache()` clears raw data** — Now also clears the stored raw API response.

  ***

  ### Fixed

  * **`initializeAndFetch: false` was ignored** — The factory used `||` instead of `??`, so passing `false` had no effect and entitlements were always fetched on initialization. Fixed to use nullish coalescing (`??`).
  * **Options table in documentation** — Filled in missing type values for `accessToken` (`string`) and `onError` (`(error: Error) => void`).
</Update>
