Skip to main content
v2.1.0
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.
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.
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.
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.
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.
if (client.isPricingLoading()) { /* show spinner */ }
getLastPricingError() — Error State
Returns the last Error from a failed pricing fetch, or null.
const err = client.getLastPricingError();
clearPricingCache() — Reset Cache
Clears cached pricing data and resets loading and error states.
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.
<span data-kq-price="pro-plan" data-kq-period="MONTHLY">Loading...</span>
await client.fetchPricing();
client.renderPricing();
 
// Scope to a container
client.renderPricing({ container: document.getElementById('pricing-section') });
 
// With format options
client.renderPricing({ formatOptions: { compact: true } });
AttributeRequiredDescription
data-kq-priceYesPlan identifier
data-kq-periodNoBilling 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.
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"
OptionTypeDefaultDescription
compactbooleanfalseUse compact notation (e.g. 1.2K)
localestringpricingLocaleOverride locale for number formatting
pricingLocalestring'en-US'Locale from the pricing API
includeCurrencySymbolbooleantruePrepend the currency symbol

New Types and Exports
export { kqFormatPrice } from './formatPrice';
export type { KQFormatPriceOptions } from './formatPrice';
export type {
  RawPricingFeature,
  RawPricingCharge,
  RawPricingPrice,
  RawPricingPlan,
  RawPricingBillingPeriod,
  RawPricingApiResponse,
} from './types';
v2.0.0
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 TypehasAccesscurrentUsageusageLimithardLimit
METERtrue if remaining > 0 or unlimitedSummedSummedtrue if any item sets it
BOOLEANtrue if any item grants access0nullfalse
CUSTOMIZABLEtrue if any item grants accessFirst item’s valueFirst item’s valueFirst 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.
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.
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.
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.
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:
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.
// 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

BeforeAfterNotes
usedcurrentUsageAligned with the raw API field name
limitusageLimitAligned with the raw API field name
typefeatureTypeNow 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).