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

# React SDK

> Documentation for the Kelviq React SDK

The `@kelviq/react-sdk` provides a seamless way to integrate kelviq entitlement management into your React applications.

## Overview

* A **React Context Provider** (`KelviqProvider`) to initialize and configure the SDK.
* **Hooks** to easily access entitlement status, pricing, and data within your components.
* **Conditional rendering components** to show or hide UI elements based on feature access.
* **Pricing components** (`KQPrice`, `KQFeatureList`) for displaying localized plan pricing and features.
* Automatic and manual **data fetching** and caching mechanisms.

The SDK fetches all entitlements for a given organization and customer, caches them, and makes them available throughout your application via React Context and custom hooks.

## **2. Installation**

Install the SDK using npm or yarn:

```bash theme={null}
npm install @kelviq/react-sdk
# or
yarn add @kelviq/react-sdk

```

You will also need `react` as a peer dependency.

## **3. Setup & Configuration**

### `KelviqProvider`

The core of the SDK is the `KelviqProvider`. You need to wrap your application (or the relevant part of it) with this provider. It initializes the SDK and makes entitlement data available to its children components via context.

```tsx theme={null}
// In your App.tsx or main application entry point
import React from 'react';
import { KelviqProvider } from '@kelviq/react-sdk';

const App = () => {
  return (
    <KelviqProvider
      customerId="your-customer-id"
      accessToken="your-access-token"
      productId="your-product-id"
      plansEnabled="pro,enterprise" // optional: filter to specific plans
      config={{
        fetchPricingOnMount: true, // fetch pricing data on mount
      }}
    >
      <MyAppContent />
    </KelviqProvider>
  );
};

const MyAppContent = () => {

  return (
    <div>
      <h1>My Application</h1>
    </div>
  );

};

export default App;

```

### Required Props for `KelviqProvider`

* `accessToken: string | null`
  * Your **Client API Key** for authenticating requests to the Kelviq API. This key is safe to expose in client-side code. You can find it in the [API keys](https://app.kelviq.com/settings/api-keys) section of your Kelviq dashboard. Never use your Server API Key in client-side code.

### Optional Props for `KelviqProvider`

<Expandable title="Optional Props">
  * `customerId?: string`
    * The ID of the customer for whom entitlements are being fetched. This will be sent as a query parameter (`customer_id`). Required for entitlements, subscriptions, and customer data. Optional when only using pricing.
  * `productId?: string`
    * The product identifier for fetching pricing/offering data. Required when `fetchPricingOnMount` is `true`.
  * `plansEnabled?: string`
    * Comma-separated list of plan identifiers to include in the pricing response. When omitted, all active plans for the product are returned. Example: `"pro,enterprise"`.
  * `apiUrl: string`
    * The base URL for your kelviq API. For example, `https://edge.api.kelviq.com/api/v1`.
  * `environment: 'sandbox' | 'production'`
    * The environment to use. If you want to use the sandbox, you must provide this.
  * `entitlementsPath: string`
    * The specific path for the endpoint that returns all entitlements for a customer. This path will be appended to `apiUrl`. For example, `entitlement-check` or `user-entitlements`.
  * `config?: KelviqApiBehaviorOptions`
    * An optional object to configure more advanced behaviors of the SDK.

  ### **Configuration Object (**`config: KelviqApiBehaviorOptions`**)**

  This optional object can contain the following properties:

  * `onError?: (error: Error) => void`
    * A callback function that will be invoked if an error occurs during the API request for entitlements.
  * `maxRetries?: number`
    * The maximum number of times to retry a failed API request. Defaults to the value set in your `apiRequest` service (typically 3).
  * `timeout?: number`
    * Timeout for API requests in milliseconds. Defaults to the value set in your `apiRequest` service (typically 5000ms).
  * `backoffBaseDelay?: number`
    * Base delay (in milliseconds) for the exponential backoff strategy used in retries. Defaults to the value set in your `apiRequest` service (typically 1000ms).
  * `fetchEntitlementsOnMount?: boolean`
    * Defaults to `true`. If true, entitlements are fetched automatically when the `KelviqProvider` mounts.
    * Set to `false` if you want to control the initial fetch manually using the `refreshAllEntitlements` function from the `useKelviq` hook.
  * `fetchSubscriptionsOnMount?: boolean`
    * Defaults to `false`. If true, the customer's subscription data is fetched automatically when the `KelviqProvider` mounts.
    * When enabled, data is available via the `useSubscriptions()` hook or `subscriptions` from `useKelviq()`.
  * `fetchCustomerOnMount?: boolean`
    * Defaults to `false`. If true, customer data is fetched automatically when the `KelviqProvider` mounts.
    * When enabled, data is available via the `useCustomer()` hook or `customer` from `useKelviq()`.
  * `fetchPricingOnMount?: boolean`
    * Defaults to `false`. If true, pricing/offering data is fetched automatically when the `KelviqProvider` mounts. Requires `productId` to be set on the provider.
    * When enabled, data is available via the `usePricing()` hook or `pricing` from `useKelviq()`.
</Expandable>

## **4. Fetching Entitlements**

### **Automatic Fetching on Mount**

By default (`fetchEntitlementsOnMount: true` in the `config` prop, or if `config` or this specific option is omitted), the SDK will attempt to fetch all entitlements for the specified `customerId` as soon as the `KelviqProvider` is mounted.

The `isLoading` state from `useKelviq()` will be `true` during this initial fetch.

### **Manual Fetching**

If `fetchEntitlementsOnMount` is set to `false`, or if you need to re-fetch entitlements at any point (e.g., after a user action that might change their entitlements), you can use the `refreshAllEntitlements` function.

<Expandable title="Code Example">
  ```tsx theme={null}
  import { useKelviq } from '@kelviq/react-sdk';

  function MyComponent() {
    const { refreshAllEntitlements, isLoading, error } = useKelviq();

    const handleRefresh = async () => {
      try {
        await refreshAllEntitlements();
        console.log("Entitlements refreshed!");
      } catch (e) {
        console.error("Failed to refresh entitlements:", e);
      }
    };

    return (
      <div>
        <button onClick={handleRefresh} disabled={isLoading}>
          {isLoading ? 'Refreshing...' : 'Refresh Entitlements'}
        </button>
        {error && <p style={{ color: 'red' }}>Error: {error.message}</p>}
        {/* ... rest of your component ... */}
      </div>
    );
  }

  ```
</Expandable>

## **5. Accessing Entitlement Data (Hooks)**

The SDK provides several hooks to access entitlement data and SDK state.

### `useKelviq()`

This is the primary hook to access the SDK's context.

* **Returns:** `KelviqContextValue` object containing:
  * `allEntitlements: AsyncState<EntitlementMap | null>`: The state object for all fetched entitlements.
    * `data`: An `EntitlementMap` (a `Record<string, Entitlement>`) where keys are `featureId`s, or `null` if not loaded or error.
    * `isLoading`: Boolean indicating if the `allEntitlements` data is currently being fetched/refreshed.
    * `error`: An `Error` object if the last fetch failed, otherwise `null`.
  * `refreshAllEntitlements: () => Promise<void>`: Function to manually trigger a re-fetch of all entitlements.
  * `getEntitlements: () => EntitlementMap`: Returns all aggregated entitlements as a `Record<string, Entitlement>` keyed by `featureId`. Returns an empty object if data has not been fetched yet.
  * `getEntitlement: (featureId: string) => Entitlement | null`: Returns the aggregated entitlement for a given `featureId`, or `null` if not found or not yet loaded.
  * `getRawEntitlements: () => EntitlementsResponse | null`: Returns the raw API response with `customerId` and the un-aggregated `entitlements` array (duplicate `featureId` entries are preserved). Returns `null` if data has not been fetched yet.
  * `getRawEntitlement: (featureId: string) => EntitlementsResponse | null`: Returns the raw API response filtered to a specific `featureId`, with `customerId` and only the matching entries.
  * `hasAccess: (featureId: string) => boolean`: A utility function to quickly check access for a feature. Returns `true` if access is granted, `false` if denied, feature not found, or data is not yet loaded.
  * `updateEntitlement: (featureId: string, data: Partial<...>) => void`: Updates a cached entitlement in-place. Accepts partial data (excluding `featureId`, `featureType`, and `items`). Automatically recalculates `remaining` when `usageLimit` or `currentUsage` are updated.
  * `subscriptions: AsyncState<RawSubscriptionData[] | null>`: The state object for the customer's subscriptions. Only populated when `fetchSubscriptionsOnMount` is `true` or after calling `refreshSubscriptions()`.
  * `refreshSubscriptions: () => Promise<void>`: Function to manually trigger a re-fetch of the customer's subscriptions.
  * `customer: AsyncState<RawCustomerApiResponse | null>`: The state object for customer data. Only populated when `fetchCustomerOnMount` is `true` or after calling `refreshCustomer()`.
  * `refreshCustomer: () => Promise<void>`: Function to manually trigger a re-fetch of the customer data.
  * `pricing: AsyncState<RawPricingApiResponse | null>`: The state object for pricing/offering data with localized currency. Only populated when `fetchPricingOnMount` is `true` or after calling `refreshPricing()`.
  * `refreshPricing: () => Promise<void>`: Function to manually trigger a re-fetch of the pricing data.
  * `isLoading: boolean`: A global loading state indicating if the SDK is performing its initial configured fetch or a refresh operation.
  * `error: Error | null`: A global error state reflecting any error from the last fetch operation initiated by the provider.
  * `customerId: string`: The customer ID passed to the provider.
  * `environment: string`: The environment (`'production'` or `'sandbox'`) passed to the provider.
  * `apiUrl: string`: The resolved API URL used by the provider.
  * `entitlementsPath: string`: The entitlements path passed to the provider.

***

#### `hasAccess(featureId)`

Quickly check if a customer has access to a feature.

```tsx theme={null}
const { hasAccess } = useKelviq();
const canUse = hasAccess("premium-reporting");
```

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

***

#### `getEntitlement(featureId)`

Returns the aggregated entitlement for a given feature. When the API returns multiple entries for the same `featureId` (e.g. from different subscriptions), the SDK groups them and exposes aggregated totals at the top level.

```tsx theme={null}
const { getEntitlement } = useKelviq();
const entitlement = getEntitlement("api-calls-quota");
```

<Accordion title="Response — Metered Entitlement">
  ```json theme={null}
  {
    "featureId": "api-calls-quota",
    "featureType": "METER",
    "hasAccess": true,
    "currentUsage": 150,
    "usageLimit": 1000,
    "remaining": 850,
    "hardLimit": true,
    "items": [
      {
        "featureId": "api-calls-quota",
        "featureType": "METER",
        "hasAccess": true,
        "resetAt": "2025-06-01T00:00:00Z",
        "hardLimit": false,
        "usageLimit": 500,
        "currentUsage": 100,
        "remaining": 400
      },
      {
        "featureId": "api-calls-quota",
        "featureType": "METER",
        "hasAccess": true,
        "resetAt": "2025-07-01T00:00:00Z",
        "hardLimit": true,
        "usageLimit": 500,
        "currentUsage": 50,
        "remaining": 450
      }
    ]
  }
  ```
</Accordion>

<Accordion title="Response — Boolean Entitlement">
  ```json theme={null}
  {
    "featureId": "enable-dark-mode",
    "featureType": "BOOLEAN",
    "hasAccess": true,
    "currentUsage": 0,
    "usageLimit": null,
    "remaining": null,
    "hardLimit": false,
    "items": [
      {
        "featureId": "enable-dark-mode",
        "featureType": "BOOLEAN",
        "hasAccess": true
      }
    ]
  }
  ```
</Accordion>

<Accordion title="Response — Customizable Entitlement">
  ```json theme={null}
  {
    "featureId": "max-items-per-page",
    "featureType": "CUSTOMIZABLE",
    "hasAccess": true,
    "currentUsage": 0,
    "usageLimit": 50,
    "remaining": 50,
    "hardLimit": false,
    "items": [
      {
        "featureId": "max-items-per-page",
        "featureType": "CUSTOMIZABLE",
        "hasAccess": true,
        "usageLimit": 50,
        "currentUsage": 0,
        "remaining": 50
      }
    ]
  }
  ```
</Accordion>

***

#### `getEntitlements()`

Returns all aggregated entitlements as a `Record<string, Entitlement>` keyed by `featureId`.

```tsx theme={null}
const { getEntitlements } = useKelviq();
const entitlements = getEntitlements();
```

<Accordion title="Response">
  ```json theme={null}
  {
    "api-calls-quota": {
      "featureId": "api-calls-quota",
      "featureType": "METER",
      "hasAccess": true,
      "currentUsage": 150,
      "usageLimit": 1000,
      "remaining": 850,
      "hardLimit": true,
      "items": [
        {
          "featureId": "api-calls-quota",
          "featureType": "METER",
          "hasAccess": true,
          "resetAt": "2025-06-01T00:00:00Z",
          "hardLimit": false,
          "usageLimit": 500,
          "currentUsage": 100,
          "remaining": 400
        },
        {
          "featureId": "api-calls-quota",
          "featureType": "METER",
          "hasAccess": true,
          "resetAt": "2025-07-01T00:00:00Z",
          "hardLimit": true,
          "usageLimit": 500,
          "currentUsage": 50,
          "remaining": 450
        }
      ]
    },
    "enable-dark-mode": {
      "featureId": "enable-dark-mode",
      "featureType": "BOOLEAN",
      "hasAccess": true,
      "currentUsage": 0,
      "usageLimit": null,
      "remaining": null,
      "hardLimit": false,
      "items": [
        {
          "featureId": "enable-dark-mode",
          "featureType": "BOOLEAN",
          "hasAccess": true
        }
      ]
    }
  }
  ```
</Accordion>

***

#### `getRawEntitlements()`

Returns the raw, un-aggregated API response with `customerId`. Duplicate `featureId` entries from multiple subscriptions are preserved as-is.

```tsx theme={null}
const { getRawEntitlements } = useKelviq();
const raw = getRawEntitlements();
```

<Accordion title="Response">
  ```json theme={null}
  {
    "customerId": "cust_456",
    "entitlements": [
      {
        "featureId": "api-calls-quota",
        "featureType": "METER",
        "hasAccess": true,
        "resetAt": "2025-06-01T00:00:00Z",
        "hardLimit": false,
        "usageLimit": 500,
        "currentUsage": 100,
        "remaining": 400
      },
      {
        "featureId": "api-calls-quota",
        "featureType": "METER",
        "hasAccess": true,
        "resetAt": "2025-07-01T00:00:00Z",
        "hardLimit": true,
        "usageLimit": 500,
        "currentUsage": 50,
        "remaining": 450
      },
      {
        "featureId": "enable-dark-mode",
        "featureType": "BOOLEAN",
        "hasAccess": true
      }
    ]
  }
  ```
</Accordion>

***

#### `getRawEntitlement(featureId)`

Returns the raw API response filtered to a specific `featureId`. Useful for accessing per-subscription data for a single feature.

```tsx theme={null}
const { getRawEntitlement } = useKelviq();
const raw = getRawEntitlement("api-calls-quota");
```

<Accordion title="Response">
  ```json theme={null}
  {
    "customerId": "cust_456",
    "entitlements": [
      {
        "featureId": "api-calls-quota",
        "featureType": "METER",
        "hasAccess": true,
        "resetAt": "2025-06-01T00:00:00Z",
        "hardLimit": false,
        "usageLimit": 500,
        "currentUsage": 100,
        "remaining": 400
      },
      {
        "featureId": "api-calls-quota",
        "featureType": "METER",
        "hasAccess": true,
        "resetAt": "2025-07-01T00:00:00Z",
        "hardLimit": true,
        "usageLimit": 500,
        "currentUsage": 50,
        "remaining": 450
      }
    ]
  }
  ```
</Accordion>

***

#### `updateEntitlement(featureId, data)`

Updates a cached entitlement in-place. Useful for optimistically updating the UI after a usage increment without waiting for a full refresh. The `remaining` field is automatically recalculated when `usageLimit` or `currentUsage` are updated.

```tsx theme={null}
const { updateEntitlement } = useKelviq();

// After the user performs an action that consumes usage
updateEntitlement("api-calls-quota", { currentUsage: 151 });
```

<Accordion title="Updated Entitlement">
  ```json theme={null}
  {
    "featureId": "api-calls-quota",
    "featureType": "METER",
    "hasAccess": true,
    "currentUsage": 151,
    "usageLimit": 1000,
    "remaining": 849,
    "hardLimit": true,
    "items": [...]
  }
  ```
</Accordion>

Accepts `Partial<Omit<Entitlement, 'featureId' | 'featureType' | 'items'>>`. You can update any combination of `hasAccess`, `currentUsage`, `usageLimit`, and `remaining`.

### `useAllEntitlements()`

A convenience hook that directly returns the `allEntitlements` state object.

* **Returns:** `AsyncState<EntitlementMap | null>`

```tsx theme={null}
import { useAllEntitlements } from '@kelviq/react-sdk';

function EntitlementsList() {
  const { data: entitlementsMap, isLoading, error } = useAllEntitlements();

  if (isLoading) return <p>Loading all entitlements...</p>;
  if (error) return <p>Error loading entitlements: {error.message}</p>;
  if (!entitlementsMap) return <p>No entitlements data.</p>;

  return (
    <ul>
      {Object.values(entitlementsMap).map(ent => (
        <li key={ent.featureId}>
          {ent.featureId}: {ent.hasAccess ? 'Enabled' : 'Disabled'} ({ent.featureType})
        </li>
      ))}
    </ul>
  );
}

```

<Accordion title="Response — entitlementsMap">
  ```json theme={null}
  {
    "api-calls-quota": {
      "featureId": "api-calls-quota",
      "featureType": "METER",
      "hasAccess": true,
      "currentUsage": 150,
      "usageLimit": 1000,
      "remaining": 850,
      "hardLimit": true,
      "items": [...]
    },
    "enable-dark-mode": {
      "featureId": "enable-dark-mode",
      "featureType": "BOOLEAN",
      "hasAccess": true,
      "currentUsage": 0,
      "usageLimit": null,
      "remaining": null,
      "hardLimit": false,
      "items": [...]
    },
    "max-items-per-page": {
      "featureId": "max-items-per-page",
      "featureType": "CUSTOMIZABLE",
      "hasAccess": true,
      "currentUsage": 0,
      "usageLimit": 50,
      "remaining": 50,
      "hardLimit": false,
      "items": [...]
    }
  }
  ```
</Accordion>

### `useBooleanEntitlement(featureId: string)`

Returns the aggregated entitlement for a specific boolean feature from the cache.

* **Arguments:**
  * `featureId: string`: The identifier of the boolean feature.
* **Returns:** `Entitlement | null`

```tsx theme={null}
import { useBooleanEntitlement } from '@kelviq/react-sdk';

const entitlement = useBooleanEntitlement("enable-dark-mode");
```

<Accordion title="Response">
  ```json theme={null}
  {
    "featureId": "enable-dark-mode",
    "featureType": "BOOLEAN",
    "hasAccess": true,
    "currentUsage": 0,
    "usageLimit": null,
    "remaining": null,
    "hardLimit": false,
    "items": [
      {
        "featureId": "enable-dark-mode",
        "featureType": "BOOLEAN",
        "hasAccess": true
      }
    ]
  }
  ```
</Accordion>

### `useCustomizableEntitlement(featureId: string)`

Returns the aggregated entitlement for a specific customizable feature from the cache.

* **Arguments:**
  * `featureId: string`: The identifier of the customizable feature.
* **Returns:** `Entitlement | null`

```tsx theme={null}
import { useCustomizableEntitlement } from '@kelviq/react-sdk';

const entitlement = useCustomizableEntitlement("max-items-per-page");
```

<Accordion title="Response">
  ```json theme={null}
  {
    "featureId": "max-items-per-page",
    "featureType": "CUSTOMIZABLE",
    "hasAccess": true,
    "currentUsage": 0,
    "usageLimit": 50,
    "remaining": 50,
    "hardLimit": false,
    "items": [
      {
        "featureId": "max-items-per-page",
        "featureType": "CUSTOMIZABLE",
        "hasAccess": true,
        "usageLimit": 50,
        "currentUsage": 0,
        "remaining": 50
      }
    ]
  }
  ```
</Accordion>

### `useMeteredEntitlement(featureId: string)`

Returns the aggregated entitlement for a specific metered feature from the cache.

* **Arguments:**
  * `featureId: string`: The identifier of the metered feature.
* **Returns:** `Entitlement | null`

```tsx theme={null}
import { useMeteredEntitlement } from '@kelviq/react-sdk';

const entitlement = useMeteredEntitlement("api-calls-quota");
```

<Accordion title="Response">
  ```json theme={null}
  {
    "featureId": "api-calls-quota",
    "featureType": "METER",
    "hasAccess": true,
    "currentUsage": 150,
    "usageLimit": 1000,
    "remaining": 850,
    "hardLimit": true,
    "items": [
      {
        "featureId": "api-calls-quota",
        "featureType": "METER",
        "hasAccess": true,
        "resetAt": "2025-06-01T00:00:00Z",
        "hardLimit": false,
        "usageLimit": 500,
        "currentUsage": 100,
        "remaining": 400
      },
      {
        "featureId": "api-calls-quota",
        "featureType": "METER",
        "hasAccess": true,
        "resetAt": "2025-07-01T00:00:00Z",
        "hardLimit": true,
        "usageLimit": 500,
        "currentUsage": 50,
        "remaining": 450
      }
    ]
  }
  ```
</Accordion>

### `useSubscriptions()`

Returns the customer's subscription data. Only populated when `fetchSubscriptionsOnMount` is enabled in the provider config, or after calling `refreshSubscriptions()` from `useKelviq()`.

* **Returns:** `AsyncState<RawSubscriptionData[] | null>`

```tsx theme={null}
import { useSubscriptions } from '@kelviq/react-sdk';

function SubscriptionInfo() {
  const { data: subscriptions, isLoading, error } = useSubscriptions();

  if (isLoading) return <p>Loading subscriptions...</p>;
  if (error) return <p>Error: {error.message}</p>;
  if (!subscriptions) return <p>No subscriptions found.</p>;

  return (
    <ul>
      {subscriptions.map(sub => (
        <li key={sub.id}>
          {sub.plan.name} — {sub.status} ({sub.recurrence})
        </li>
      ))}
    </ul>
  );
}
```

<Accordion title="Response — subscriptions">
  ```json theme={null}
  [
    {
      "id": "sub-uuid-1",
      "externalSubscriptionId": "ext-sub-123",
      "startDate": "2025-01-01T00:00:00Z",
      "endDate": null,
      "billingPeriodStartTime": "2025-06-01T00:00:00Z",
      "billingPeriodEndTime": "2025-07-01T00:00:00Z",
      "amount": "49.99",
      "recurrence": "monthly",
      "currency": "USD",
      "status": "active",
      "product": {
        "name": "Pro Plan",
        "identifier": "pro-plan"
      },
      "plan": {
        "name": "Pro Monthly",
        "identifier": "pro-monthly"
      },
      "features": [
        {
          "featureId": "api-calls-quota",
          "featureType": "METER",
          "hasAccess": true,
          "usageLimit": 1000,
          "currentUsage": 150,
          "remaining": 850
        }
      ],
      "trialDaysRemaining": null,
      "customerId": "cust_456"
    }
  ]
  ```
</Accordion>

<Note>
  You must enable `fetchSubscriptionsOnMount: true` in the provider's `config` prop, or call `refreshSubscriptions()` manually, for this hook to return data.
</Note>

### `useCustomer()`

Returns the customer data. Only populated when `fetchCustomerOnMount` is enabled in the provider config, or after calling `refreshCustomer()` from `useKelviq()`.

* **Returns:** `AsyncState<RawCustomerApiResponse | null>`

```tsx theme={null}
import { useCustomer } from '@kelviq/react-sdk';

function CustomerProfile() {
  const { data: customer, isLoading, error } = useCustomer();

  if (isLoading) return <p>Loading customer...</p>;
  if (error) return <p>Error: {error.message}</p>;
  if (!customer) return <p>No customer data.</p>;

  return (
    <div>
      <h2>{customer.name}</h2>
      <p>{customer.email}</p>
      {customer.billingAddress && (
        <p>{customer.billingAddress.city}, {customer.billingAddress.state}</p>
      )}
    </div>
  );
}
```

<Accordion title="Response — customer">
  ```json theme={null}
  {
    "id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
    "customerId": "unique-customer-id-123",
    "name": "John Doe",
    "email": "customer@example.com",
    "details": {},
    "metadata": {
      "source": "sdk_import",
      "priority": "high"
    },
    "billingAddress": {
      "country": "IN",
      "line1": "123 Main Street",
      "line2": "Apt 4B",
      "postalCode": "560001",
      "city": "Bangalore",
      "state": "Karnataka"
    },
    "createdOn": "2025-06-04T06:03:30.195790Z",
    "modifiedOn": "2025-06-04T06:03:30.195831Z"
  }
  ```
</Accordion>

<Note>
  You must enable `fetchCustomerOnMount: true` in the provider's `config` prop, or call `refreshCustomer()` manually, for this hook to return data.
</Note>

### `usePricing()`

Returns the pricing/offering data for the configured product. Includes localized currency information, plans, features, and billing periods. Only populated when `fetchPricingOnMount` is enabled in the provider config, or after calling `refreshPricing()` from `useKelviq()`.

* **Returns:** `AsyncState<RawPricingApiResponse | null>`

```tsx theme={null}
import { usePricing } from '@kelviq/react-sdk';

function PricingDisplay() {
  const { data: pricing, isLoading, error } = usePricing();

  if (isLoading) return <p>Loading pricing...</p>;
  if (error) return <p>Error: {error.message}</p>;
  if (!pricing) return <p>No pricing data.</p>;

  return (
    <div>
      <p>Currency: {pricing.currencySymbol} ({pricing.currencyCode})</p>
      <ul>
        {pricing.plans.map(plan => (
          <li key={plan.id}>
            {plan.displayName} — {plan.price.priceType}
          </li>
        ))}
      </ul>
    </div>
  );
}
```

<Note>
  You must set `productId` on the provider and enable `fetchPricingOnMount: true` in the provider's `config` prop, or call `refreshPricing()` manually, for this hook to return data.

  When `customerId` is provided, the pricing response includes the customer's active subscription details and the `isCustomerExists` flag. Use `plansEnabled` to filter the response to specific plans (e.g., `"pro,enterprise"`).
</Note>

**Example using a specific entitlement hook:**

```tsx theme={null}
import { useBooleanEntitlement, useKelviq } from '@kelviq/react-sdk';

function FeatureSpecificComponent({ featureId }: { featureId: string }) {
  const { isLoading, error } = useKelviq();
  const entitlement = useBooleanEntitlement(featureId);

  if (isLoading) return <p>Loading feature {featureId}...</p>;
  if (error) return <p>Error loading feature {featureId}: {error.message}</p>;

  if (entitlement && entitlement.hasAccess) {
    return <p>You have access to {featureId}!</p>;
  } else {
    return <p>You do not have access to {featureId}.</p>;
  }
}

```

## **6. Conditional Rendering Components**

These components provide a declarative way to render UI based on entitlement status. They internally use the respective hooks.

**Common Props:**

* `featureId: string`: The unique identifier of the feature to check.
* `children: ReactNode | ((data: Entitlement) => ReactNode)`:
  * If a `ReactNode`, it's rendered when the user is entitled and conditions are met.
  * If a function (render prop), it's called with the aggregated `Entitlement` data and its return value is rendered.
* `fallback?: ReactNode`: Content to render if the user is not entitled, or if data is loading (and no `loadingComponent` is provided), or if an error occurs. Defaults to `null`.
* `loadingComponent?: ReactNode`: Specific content to render while the entitlement data is loading. Overrides `fallback` during loading.

### `ShowWhenBooleanEntitled`

Renders children if the boolean feature is enabled (`hasAccess: true`).

```tsx theme={null}
import { ShowWhenBooleanEntitled } from '@kelviq/react-sdk';

<ShowWhenBooleanEntitled
  featureId="enable-dark-mode"
  loadingComponent={<p>Checking theme settings...</p>}
  fallback={<p>Dark mode is not available.</p>}
>
  <button>Toggle Dark Mode</button>
</ShowWhenBooleanEntitled>

<ShowWhenBooleanEntitled featureId="show-advanced-settings">
  {(entitlementData) => (
    <div>Advanced settings for {entitlementData.featureId} are visible!</div>
  )}
</ShowWhenBooleanEntitled>

```

<Accordion title="Render Prop Data (entitlementData)">
  ```json theme={null}
  {
    "featureId": "show-advanced-settings",
    "featureType": "BOOLEAN",
    "hasAccess": true,
    "currentUsage": 0,
    "usageLimit": null,
    "remaining": null,
    "hardLimit": false,
    "items": [
      {
        "featureId": "show-advanced-settings",
        "featureType": "BOOLEAN",
        "hasAccess": true
      }
    ]
  }
  ```
</Accordion>

### `ShowWhenCustomizableEntitled`

Renders children if the customizable feature is enabled (`hasAccess: true`).

```tsx theme={null}
import { ShowWhenCustomizableEntitled } from '@kelviq/react-sdk';

<ShowWhenCustomizableEntitled
  featureId="max-items-per-page"
  loadingComponent={<p>Loading display settings...</p>}
  fallback={<p>Default item limit applies.</p>}
>
  {(entitlementData) => (
    <p>You have access to {entitlementData.featureId}.</p>
  )}
</ShowWhenCustomizableEntitled>

```

<Accordion title="Render Prop Data (entitlementData)">
  ```json theme={null}
  {
    "featureId": "max-items-per-page",
    "featureType": "CUSTOMIZABLE",
    "hasAccess": true,
    "currentUsage": 0,
    "usageLimit": 50,
    "remaining": 50,
    "hardLimit": false,
    "items": [
      {
        "featureId": "max-items-per-page",
        "featureType": "CUSTOMIZABLE",
        "hasAccess": true,
        "usageLimit": 50,
        "currentUsage": 0,
        "remaining": 50
      }
    ]
  }
  ```
</Accordion>

### `ShowWhenMeteredEntitled`

Renders children if the metered feature is enabled and, by default, if `remaining` usage is greater than 0 or unlimited (`usageLimit` is `null`).

* **Optional Prop:**
  * `condition?: (data: Entitlement) => boolean`: A custom function to further determine if children should render based on the entitlement data.
* **Children Prop:** Can be `ReactNode` or `(data: Entitlement) => ReactNode`.

```tsx theme={null}
import { ShowWhenMeteredEntitled } from '@kelviq/react-sdk';

<ShowWhenMeteredEntitled
  featureId="api-calls-quota"
  loadingComponent={<p>Loading API quota...</p>}
  fallback={<p>API call limit reached or feature not available.</p>}
>
  {(meterData) => (
    <div>
      <p>API Calls Used: {meterData.currentUsage} / {meterData.usageLimit === null ? 'Unlimited' : meterData.usageLimit}</p>
      <p>Remaining: {meterData.remaining === null ? 'Unlimited' : meterData.remaining}</p>
    </div>
  )}
</ShowWhenMeteredEntitled>

```

<Accordion title="Render Prop Data (meterData)">
  ```json theme={null}
  {
    "featureId": "api-calls-quota",
    "featureType": "METER",
    "hasAccess": true,
    "currentUsage": 150,
    "usageLimit": 1000,
    "remaining": 850,
    "hardLimit": true,
    "items": [
      {
        "featureId": "api-calls-quota",
        "featureType": "METER",
        "hasAccess": true,
        "resetAt": "2025-06-01T00:00:00Z",
        "hardLimit": false,
        "usageLimit": 500,
        "currentUsage": 100,
        "remaining": 400
      },
      {
        "featureId": "api-calls-quota",
        "featureType": "METER",
        "hasAccess": true,
        "resetAt": "2025-07-01T00:00:00Z",
        "hardLimit": true,
        "usageLimit": 500,
        "currentUsage": 50,
        "remaining": 450
      }
    ]
  }
  ```
</Accordion>

## **7. Pricing Components**

The SDK provides render-prop components for displaying pricing data. These require `productId` on the provider and `fetchPricingOnMount: true` (or a manual `refreshPricing()` call).

### `KQPrice`

Renders pricing information for a specific plan and billing period. Provides a `formattedPrice` string with locale-aware number formatting and currency symbol.

**Props:**

* `planIdentifier: string`: The plan identifier to display pricing for.
* `billingPeriod: string`: The billing period to show (e.g. `"MONTHLY"`, `"YEARLY"`, `"ONE_TIME"`).
* `children: (data) => ReactNode`: Render function receiving price data.
* `formatOptions?: KQFormatPriceOptions`: Options for the `formattedPrice` string.
* `loadingComponent?: ReactNode`: Shown while pricing data is loading.
* `fallback?: ReactNode`: Shown when the plan is not found or pricing data is unavailable.

```tsx theme={null}
import { KQPrice } from '@kelviq/react-sdk';

<KQPrice
  planIdentifier="pro-plan"
  billingPeriod="MONTHLY"
  fallback={<p>Price not available</p>}
>
  {({ formattedPrice, isFree, hasFreeTrial, trialPeriod }) => (
    <div>
      <h3>{formattedPrice}</h3>
      {hasFreeTrial && <p>{trialPeriod}-day free trial</p>}
    </div>
  )}
</KQPrice>
```

<Accordion title="Render Prop Data">
  The `children` render function receives an object with:

  * `plan`: The full plan object (`RawPricingPlan`).
  * `charge`: The matching charge for the billing period (`RawPricingCharge | null`).
  * `amount: number`: The raw price amount.
  * `formattedPrice: string`: Locale-formatted price string (e.g. `"$49.99"`, `"Free"`).
  * `currencySymbol: string`: The currency symbol (e.g. `"$"`, `"€"`).
  * `currencyCode: string`: The currency code (e.g. `"USD"`, `"EUR"`).
  * `pricingLocale: string`: The locale used for pricing (e.g. `"en-US"`).
  * `isFree: boolean`: Whether the plan is free.
  * `hasFreeTrial: boolean`: Whether the plan has a free trial.
  * `trialPeriod: number`: Trial period in days.
</Accordion>

#### `KQFormatPriceOptions`

Options for customizing the `formattedPrice` string:

* `compact?: boolean`: Use compact notation (e.g. `"1.2K"` instead of `"1,200"`). Defaults to `false`.
* `locale?: string`: Locale for number formatting. Defaults to the `pricingLocale` from the API.
* `includeCurrencySymbol?: boolean`: Whether to include the currency symbol. Defaults to `true`.

### `KQFeatureList`

Iterates over features for a specific plan, calling the render function for each feature.

**Props:**

* `planIdentifier: string`: The plan identifier to display features for.
* `children: (data) => ReactNode`: Render function called for each feature.
* `featureType?: 'BOOLEAN' | 'METER'`: Optional filter to show only specific feature types.
* `loadingComponent?: ReactNode`: Shown while pricing data is loading.
* `fallback?: ReactNode`: Shown when the plan is not found or has no features.

```tsx theme={null}
import { KQFeatureList } from '@kelviq/react-sdk';

<KQFeatureList
  planIdentifier="pro-plan"
  fallback={<p>No features</p>}
>
  {({ feature }) => (
    <div key={feature.id}>
      <span>{feature.displayName}</span>
      <span>
        {feature.featureType === 'BOOLEAN'
          ? feature.value ? '✓' : '✗'
          : feature.value}
      </span>
    </div>
  )}
</KQFeatureList>
```

<Accordion title="Render Prop Data">
  The `children` render function receives an object with:

  * `feature`: The feature object (`RawPricingFeature`) with `id`, `displayName`, `featureType`, `value`, `enabled`.
  * `plan`: The full plan object (`RawPricingPlan`).
  * `index: number`: The index of the feature in the list.
</Accordion>

### `kqFormatPrice(amount, currencySymbol, options?)`

A standalone utility function for formatting prices. Exported from the SDK for use outside of components.

```tsx theme={null}
import { kqFormatPrice } from '@kelviq/react-sdk';

kqFormatPrice(1299.99, '$');              // "$1,299.99"
kqFormatPrice(1299.99, '$', { compact: true }); // "$1.3K"
kqFormatPrice(49.99, '€', { locale: 'de-DE' }); // "€49,99"
kqFormatPrice(49.99, '$', { includeCurrencySymbol: false }); // "49.99"
```

## **8. Key Types**

The SDK exports several types for better integration with TypeScript. Some key ones include:

* `KelviqContextValue`: The shape of the object returned by `useKelviq()`.
* `KelviqApiBehaviorOptions`: Configuration options for the `config` prop.
* `AsyncState<T>`: Generic type for asynchronous data states (`{ data, isLoading, error }`).
* `Entitlement`: The unified entitlement type with aggregated data and an `items` array containing the raw API objects.
* `EntitlementMap`: The structure of the cached entitlements (`Record<string, Entitlement>`), keyed by `featureId`.
* `EntitlementsResponse`: The raw API response shape (`{ customerId, entitlements }`) returned by `getRawEntitlements()` and `getRawEntitlement()`.
* `RawEntitlementsApiResponse` and related `Raw...` types: Represent the structure of the direct API response.
* `RawSubscriptionData`: A single subscription object as returned by the subscriptions API, including `plan`, `product`, `features`, `status`, `amount`, `recurrence`, etc.
* `RawCustomerApiResponse`: Customer data as returned by the customer API, including `id`, `customerId`, `name`, `email`, `billingAddress`, `details`, `metadata`, `createdOn`, and `modifiedOn`.
* `RawBillingAddress`: The billing address object (`country`, `line1`, `line2`, `postalCode`, `city`, `state`).
* `RawPricingApiResponse`: The full pricing/offering API response including `plans`, `currencySymbol`, `currencyCode`, `pricingLocale`, and `billingPeriods`.
* `RawPricingPlan`: A single plan with `identifier`, `displayName`, `displayDescription`, `price`, `features`, and `enabled`.
* `RawPricingFeature`: A plan feature with `id`, `displayName`, `featureType`, `value`, and `enabled`.
* `RawPricingCharge`: A charge entry with `chargePeriod` and `priceData` (containing `amount`).
* `KQFormatPriceOptions`: Options for the `kqFormatPrice` utility (`compact`, `locale`, `includeCurrencySymbol`).

You can import these types directly from the main SDK entry:

```ts theme={null}
import type {
  Entitlement,
  EntitlementMap,
  EntitlementsResponse,
  RawSubscriptionData,
  RawCustomerApiResponse,
  RawBillingAddress,
  RawPricingApiResponse,
  RawPricingPlan,
  RawPricingFeature,
  RawPricingCharge,
  KQFormatPriceOptions,
} from '@kelviq/react-sdk';
```

## **9. Error Handling**

* **Provider Level:** The `onError` callback in `config` (`KelviqApiBehaviorOptions`) can be used to globally handle errors that occur during the entitlement fetching process initiated by the provider.
* **Context Level:** `useKelviq()` returns a global `isLoading: boolean` and `error: Error | null` state, reflecting the provider's most recent fetch operation. Use these for loading/error UI in your components.
* **`allEntitlements` Level:** The `allEntitlements` object from `useKelviq()` or `useAllEntitlements()` also carries its own `isLoading` and `error` fields within the `AsyncState` wrapper.

## **10. Example Usage**

A comprehensive example is provided within the comments of the SDK code, demonstrating provider setup and component usage. The core setup involves:

1. Wrapping your application with `KelviqProvider` and providing the required props (`customerId`, `accessToken`)
2. Using hooks like `useBooleanEntitlement` or conditional components like `ShowWhenBooleanEntitled` in your components to control UI and behavior based on entitlements.

## **11. API Request Service**

The SDK relies on an `apiRequest` function for making HTTP requests. The SDK's `KelviqProvider` passes configuration like `timeout`, `maxRetries`, `backoffBaseDelay`, and `accessToken` to this function.

* **Your Responsibility:** You need to ensure that your project includes an implementation of this `apiRequest` service (e.g., in `src/services/makeRequest.service.ts`) that matches the `ApiRequestConfig` and `ApiResponse` interfaces used by the SDK. This service should handle actual HTTP communication, including appending query parameters passed via `ApiRequestConfig.queryParams` and using the `accessToken` to set the `Authorization` header.
* The placeholder `apiRequest` function included in the SDK's source code is for demonstration and type-checking purposes only and will not make real API calls.

## **12. Project Structure (for contributors/reference)**

The SDK source code (`lib/`) is organized as follows:

* `components/`: Contains conditional rendering and pricing React components.
  * `ShowWhenBooleanEntitled.tsx`
  * `ShowWhenCustomizableEntitled.tsx`
  * `ShowWhenMeteredEntitled.tsx`
  * `ShowWhenEntitledInternal.tsx` (Internal helper, not typically exported)
  * `KQPrice.tsx`
  * `KQFeatureList.tsx`
* `contexts/`: Defines the React Context and Provider.
  * `KelviqContext.ts`
  * `KelviqProvider.tsx`
* `hooks/`: Contains all custom React Hooks.
  * `useKelviq.ts`
  * `useAllEntitlements.ts`
  * `useBooleanEntitlement.ts`
  * `useCustomizableEntitlement.ts`
  * `useMeteredEntitlement.ts`
  * `useSubscriptions.ts`
  * `useCustomer.ts`
  * `usePricing.ts`
  * `index.ts` (Exports all hooks)
* `types/`: Contains all TypeScript definitions.
  * `api.types.ts` (Raw types from API, including pricing types)
  * `sdk.types.ts` (Processed types for SDK and public use)
  * `index.ts` (Re-exports all types)
* `utils/`: Contains utility functions.
  * `transformations.ts` (For `transformApiEntitlements`)
  * `formatPrice.ts` (For `kqFormatPrice`)
  * `index.ts` (Exports utilities)
* `index.ts`: The main entry point of the SDK, re-exporting all public APIs (provider, hooks, components, types).

## Using the Sandbox

If you want to use the sandbox, you need to set the `environment` option to `sandbox` in the `KelviqProvider`.

```tsx theme={null}
<KelviqProvider 
  customerId="YOUR_CUSTOMER_ID"
  accessToken="YOUR_ACCESS_TOKEN"
  environment="sandbox">
      {/* Your app components */}
</KelviqProvider>
```
