The @kelviq/js-sdk is a lightweight, plain TypeScript library designed for interacting with Kelviq entitlement and pricing services. It allows you to:
- Fetch all feature entitlements for a specific organization and customer.
- Cache these entitlements client-side for efficient and repeated access.
- Check if a user has access to a particular feature.
- Retrieve specific entitlement details, including boolean flags, numeric configurations, and metered usage data.
- Fetch product offering and pricing data for display on pricing pages.
- Format prices with locale-aware currency formatting.
- Automatically populate pricing into DOM elements via data attributes.
- Manually refresh entitlement and pricing data from the server.
If you are using React, you can use the
React SDK instead.
Installation
Install the SDK using your preferred package manager or include it directly via CDN.
npm install @kelviq/js-sdk
# or
yarn add @kelviq/js-sdk
# or
pnpm add @kelviq/js-sdk
CDN Installation:
<script src="https://unpkg.com/@kelviq/js-sdk@2.1.0/dist/kelviq-js-sdk.umd.js"></script>
Basic Usage
Get started with the SDK in just a few lines of code.
import kelviqSDK from '@kelviq/js-sdk';
// Initialize the SDK (fetches entitlements automatically)
const kq = kelviqSDK({
customerId: "your-customer-id",
accessToken: "your-access-token",
});
// Wait for the initial fetch to complete
await kq.ready();
// Check if user has access to a feature
if (kq.hasAccess('premium-features')) {
console.log("User has access to premium features!");
}
// Get specific entitlement details
const emailQuota = kq.getEntitlement('email-sends');
if (emailQuota && emailQuota.hasAccess) {
console.log(`Emails remaining: ${emailQuota.remaining}`);
}
API Reference
Complete documentation of all SDK methods and configuration options.
Initialization
Create and configure your SDK instance with the required parameters.
Security Warning: Never use your Server API Key in the browser. Use the Client API Key which is safe to expose in client-side code. You can find both keys in the API keys section of your Kelviq dashboard.
// Initialize the SDK
const kq = kelviqSDK({
customerId: "your-customer-id",
accessToken: "your-access-token",
});
You can also initialize the SDK with just a productId to fetch pricing data only (without entitlements):
// Initialize for pricing only
const kq = kelviqSDK({
productId: "your-product-id",
accessToken: "your-access-token",
});
await kq.ready();
const pricing = kq.getPricing();
Or use both customerId and productId together:
// Initialize for both entitlements and pricing
const kq = kelviqSDK({
customerId: "your-customer-id",
productId: "your-product-id",
accessToken: "your-access-token",
});
await kq.ready();
// Both entitlements and pricing are now available
Options
| Option | Type | Description | |
|---|
customerId | string | Unique identifier for the customer (required for entitlements) | |
productId | string | Product identifier for fetching pricing data (required for pricing) | |
accessToken | string | Authentication token for API requests | |
environment | sandbox|production | ’production’ | The environment to use. If you want to use the sandbox, you must provide this. |
initializeAndFetch | boolean | true | Automatically fetch data on initialization |
onError | (error: Error) => void | Error callback function | |
mainApiUrl | string | Optional override for the main API base URL (used by pricing) | |
At least one of customerId or productId must be provided.
Core Methods
hasAccess(featureId: string): boolean
Check if the user has access to a specific feature. Returns false if the feature doesn’t exist or data hasn’t been fetched.
if (kq.hasAccess('premium-features')) {
// Show premium features
showPremiumUI();
}
getEntitlement(featureId: string): Entitlement | null
Get the aggregated entitlement for a specific feature. Returns the unified Entitlement object which includes the .items array of raw API objects.
// Boolean entitlement
const canUseAnalytics = kq.getEntitlement('analytics');
if (canUseAnalytics?.hasAccess) {
enableAnalytics();
}
// Metered entitlement
const emailQuota = kq.getEntitlement('email-sends');
if (emailQuota?.hasAccess && emailQuota.remaining !== null && emailQuota.remaining > 0) {
console.log(`Usage: ${emailQuota.currentUsage} / ${emailQuota.usageLimit}`);
console.log(`Emails remaining: ${emailQuota.remaining}`);
}
getEntitlements(): Record<string, Entitlement> | null
Get all aggregated entitlements as a map keyed by featureId.
const allEntitlements = kq.getEntitlements();
if (allEntitlements) {
Object.entries(allEntitlements).forEach(([featureId, entitlement]) => {
console.log(`${featureId}: ${entitlement.hasAccess ? 'Access granted' : 'Access denied'}`);
});
}
getRawEntitlement(featureId: string): RawEntitlement[] | null
Get the raw, un-aggregated API items for a specific feature. Useful when you need per-item details like resetAt or hardLimit.
const rawItems = kq.getRawEntitlement('email-sends');
if (rawItems) {
rawItems.forEach(item => {
console.log(`Reset at: ${item.resetAt}, Limit: ${item.usageLimit}`);
});
}
getRawEntitlements(): RawEntitlementsApiResponse | null
Get the full raw API response including the customerId wrapper, exactly as received from the server.
const raw = kq.getRawEntitlements();
if (raw) {
console.log(`Customer: ${raw.customerId}`);
console.log(`Total items: ${raw.entitlements.length}`);
}
ready(): Promise<void>
Wait for the initial fetch (triggered by initializeAndFetch: true) to complete. Resolves immediately if no initial fetch was triggered or if the data is already available.
const kq = kelviqSDK({
customerId: "your-customer-id",
accessToken: "your-access-token",
});
await kq.ready();
// Entitlements are now available
if (kq.hasAccess('premium-features')) {
showPremiumUI();
}
fetchAllEntitlements(forceRefresh?: boolean): Promise<Record<string, Entitlement>>
Manually trigger a network request to refresh the entitlements. If a fetch is already in progress, the same promise is returned (calls are deduplicated).
// Fetch fresh data
try {
const entitlements = await kq.fetchAllEntitlements(true);
console.log('Entitlements refreshed:', entitlements);
} catch (error) {
console.error('Failed to refresh entitlements:', error);
}
isLoading(): boolean
Check if entitlements are currently being fetched.
if (kq.isLoading()) {
showLoadingSpinner();
} else {
hideLoadingSpinner();
}
getLastError(): Error | null
Get the last error that occurred during entitlement fetching.
const lastError = kq.getLastError();
if (lastError) {
console.error('Last SDK error:', lastError.message);
}
clearCache(): void
Clear the cached entitlements.
kq.clearCache();
// Next call to hasAccess or getEntitlement will need a new fetch
Pricing Methods
fetchPricing(forceRefresh?: boolean): Promise<RawPricingApiResponse>
Fetch product offering and pricing data. Requires productId to be set. Results are cached; use forceRefresh to bypass the cache.
const pricing = await kq.fetchPricing();
console.log(`Plans: ${pricing.plans.length}`);
console.log(`Currency: ${pricing.currencySymbol}`);
getPricing(): RawPricingApiResponse | null
Get cached pricing data. Returns null if not yet fetched.
const pricing = kq.getPricing();
if (pricing) {
pricing.plans.forEach(plan => {
console.log(`${plan.displayName}: ${plan.price.priceType}`);
});
}
getPlan(identifier: string): RawPricingPlan | null
Find an enabled plan by its identifier from cached pricing data.
const starterPlan = kq.getPlan('starter');
if (starterPlan) {
const monthlyCharge = starterPlan.price.charges.find(c => c.chargePeriod === 'MONTHLY');
console.log(`Monthly price: ${monthlyCharge?.priceData.amount}`);
}
isPricingLoading(): boolean
Check if pricing data is currently being fetched.
getLastPricingError(): Error | null
Get the last error from a pricing fetch.
clearPricingCache(): void
Clear cached pricing data and reset pricing state.
kq.clearPricingCache();
// Next call to getPricing will need a new fetch
renderPricing() — DOM Binding
Automatically populate pricing into HTML elements using data-kq-price attributes. The SDK scans the DOM for elements with these attributes and fills in formatted prices.
<span data-kq-price="starter" data-kq-period="MONTHLY">Loading...</span>
<span data-kq-price="starter" data-kq-period="ANNUALLY">Loading...</span>
<span data-kq-price="free" data-kq-period="MONTHLY">Loading...</span>
const kq = kelviqSDK({
productId: "your-product-id",
accessToken: "your-access-token",
});
await kq.ready();
kq.renderPricing(); // Scans DOM and populates data-kq-price elements
data-kq-price — the plan identifier (e.g., "starter", "pro")
data-kq-period — the charge period (e.g., "MONTHLY", "ANNUALLY")
- Free plans display
"Free" instead of a price
- Disabled or missing plans result in empty text
You can scope the scan to a container and pass formatting options:
kq.renderPricing({
container: document.getElementById('pricing-section'),
formatOptions: { compact: true },
});
A standalone utility for formatting prices with locale-aware currency formatting.
import { kqFormatPrice } from '@kelviq/js-sdk';
kqFormatPrice(49.99, '$'); // "$49.99"
kqFormatPrice(1299, '$', { compact: true }); // "$1.3K"
kqFormatPrice(100, '€', { locale: 'de-DE' }); // "€100"
kqFormatPrice(29, '$', { includeCurrencySymbol: false }); // "29"
| Option | Type | Default | Description |
|---|
compact | boolean | false | Use compact notation (e.g., “1.2K”) |
locale | string | 'en-US' | Locale for number formatting |
includeCurrencySymbol | boolean | true | Include the currency symbol |
pricingLocale | string | — | Fallback locale (from pricing API response) |
Entitlement Types
The SDK uses a single, unified Entitlement interface. When the API returns duplicate featureIds (e.g., from Subscriptions + Top-ups), they are aggregated automatically.
interface Entitlement {
featureId: string;
featureType: "METER" | "BOOLEAN" | "CUSTOMIZABLE";
hasAccess: boolean; // METER: remaining === null || remaining > 0; others: true if any item grants access
hardLimit: boolean; // true if any item has a hard limit
currentUsage: number; // METER: summed across items; CUSTOMIZABLE: from first item; BOOLEAN: 0
usageLimit: number | null; // METER: summed across items; CUSTOMIZABLE: from first item; BOOLEAN: null
remaining: number | null; // METER: usageLimit - currentUsage; CUSTOMIZABLE: from first item; BOOLEAN: null
items: RawEntitlement[]; // The raw array of original API objects for this featureId
}
Per-item details such as resetAt and hardLimit are available on each item in the items array.
Pricing Types
The pricing API returns a rich data structure with plans, charges, features, and billing periods.
interface RawPricingApiResponse {
organizationId: number;
countryCode: string;
pricingLocale: string;
currencyCode: string;
currencySymbol: string;
currencyConversionRate: number;
baseCurrencyCode: string;
baseCurrencySymbol: string;
plans: RawPricingPlan[];
subscription: { hasPreviousSubscription: boolean };
isCustomerExists: boolean;
billingPeriods: RawPricingBillingPeriod[];
}
interface RawPricingPlan {
name: string;
identifier: string;
displayName: string;
displayDescription: string;
price: RawPricingPrice;
features: RawPricingFeature[];
enabled: boolean;
shouldHighlight: boolean;
// ... additional fields
}
interface RawPricingPrice {
priceType: 'FREE' | 'PAID';
charges: RawPricingCharge[];
freeTrial: boolean;
trialPeriod: number;
currency: string;
}
interface RawPricingCharge {
chargePeriod: string; // e.g., "MONTHLY", "ANNUALLY"
priceData: { amount: number };
}
All pricing types are exported from the SDK: RawPricingApiResponse, RawPricingPlan, RawPricingPrice, RawPricingCharge, RawPricingFeature, RawPricingBillingPeriod.
Examples
Real-world usage patterns and common implementation scenarios.
Feature Gating
Control access to features based on user entitlements.
// Simple feature gate
if (kq.hasAccess('beta-features')) {
showBetaFeatures();
}
// Conditional rendering
const canUseAdvancedSearch = kq.hasAccess('advanced-search');
return (
<div>
<BasicSearch />
{canUseAdvancedSearch && <AdvancedSearch />}
</div>
);
Usage-Based Features
Implement usage limits and quota management.
const apiCalls = kq.getEntitlement('api-calls');
if (apiCalls?.hasAccess) {
if (apiCalls.remaining === null || apiCalls.remaining > 0) {
// Allow API call
makeApiCall();
console.log(`Usage: ${apiCalls.currentUsage} / ${apiCalls.usageLimit}`);
} else {
// Show upgrade message
showUpgradeMessage();
}
}
Configuration-Based Features
Apply dynamic configuration based on user entitlements.
const teamSizeEntitlement = kq.getEntitlement('max-team-size');
if (teamSizeEntitlement?.hasAccess) {
if (currentTeam.length >= (teamSizeEntitlement.usageLimit ?? Infinity)) {
showUpgradePrompt(); // or disable the "Add Member" button
} else {
enableTeamInvite();
}
}
Accessing Per-Item Details
When you need item-level details like resetAt or hardLimit, use the items array or the getRawEntitlement method.
// Via the aggregated entitlement's items array
const emailQuota = kq.getEntitlement('email-sends');
if (emailQuota) {
emailQuota.items.forEach(item => {
console.log(`Reset at: ${item.resetAt}, Hard limit: ${item.hardLimit}`);
});
}
// Or via getRawEntitlement
const rawItems = kq.getRawEntitlement('email-sends');
if (rawItems) {
rawItems.forEach(item => {
console.log(`Reset at: ${item.resetAt}, Hard limit: ${item.hardLimit}`);
});
}
Error Handling
Robust error handling for production applications.
const kq = kelviqSDK({
customerId: "user123",
accessToken: "token456",
onError: (error) => {
// Log to monitoring service
logError('kelviq SDK Error', error);
// Show user-friendly message
showNotification('Unable to load feature settings', 'error');
}
});
// Handle fetch errors
try {
await kq.fetchAllEntitlements();
} catch (error) {
console.error('Failed to load entitlements:', error);
// Fallback to default behavior
showDefaultFeatures();
}
Custom API Request Service
Integrate with your existing HTTP client or add custom logic.
// Custom API request function
async function customApiRequest<T>(config: ApiRequestConfig): Promise<ApiResponse<T>> {
const response = await fetch(config.url, {
method: config.method || 'GET',
headers: {
'Authorization': `Bearer ${config.accessToken}`,
'Content-Type': 'application/json',
...config.headers
},
body: config.body ? JSON.stringify(config.body) : undefined
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return {
status: response.status,
statusText: response.statusText,
data: await response.json()
};
}
// Use custom API request
const kq = kelviqSDK({
customerId: "user123",
accessToken: "token456"
}, customApiRequest);
TypeScript Support
Full TypeScript support with comprehensive type definitions.
The SDK is written in TypeScript and provides full type safety:
import kelviqSDK, {
KelviqClientOptions,
Entitlement,
RawEntitlement,
RawEntitlementsApiResponse
} from '@kelviq/js-sdk';
const options: KelviqClientOptions = {
customerId: "user123",
accessToken: "token456"
};
const kq = kelviqSDK(options);
// Type-safe entitlement access
const emailQuota: Entitlement | null = kq.getEntitlement('emails');
Advanced Configuration
Advanced options for customizing SDK behavior and API integration.
API Configuration
Configure API request behavior and retry logic.
const kq = kelviqSDK({
customerId: "user123",
accessToken: "token456",
apiConfig: {
maxRetries: 5,
timeout: 10000,
backoffBaseDelay: 2000
}
});
Custom Error Handling
Implement sophisticated error handling and recovery strategies.
const kq = kelviqSDK({
customerId: "user123",
accessToken: "token456",
onError: (error) => {
// Send to error tracking service
Sentry.captureException(error);
// Retry logic
if (error.message.includes('network')) {
setTimeout(() => kq.fetchAllEntitlements(), 5000);
}
}
});
Advanced Options
Additional configuration options for specialized use cases.
Default values are:
const kq = kelviqSDK({
customerId: "",
accessToken: "",
apiUrl: "https://edge.api.kelviq.com/api/v1",
entitlementsPath: "entitlements/",
apiConfig: {
maxRetries: 3,
timeout: 5000,
backoffBaseDelay: 1000
}
});
| Option | Type | Description |
|---|
apiUrl | string | Base URL for the API |
entitlementsPath | string | Path for fetching entitlements |
apiConfig.maxRetries | number | Maximum number of retries for API requests |
apiConfig.timeout | number | Timeout for API requests in milliseconds |
apiConfig.backoffBaseDelay | number | Base delay for exponential backoff strategy |
Using the Sandbox
If you want to use the sandbox, you need to set the environment option to sandbox in the init function.
const kq = kelviqSDK({
customerId: "your-customer-id",
accessToken: "your-access-token",
environment: "sandbox"
});