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

# Features and Entitlements

> Define product capabilities and control customer access

Features represent the capabilities you bundle with your products — things like "SSO enabled", "Premium support", or "100GB storage". When customers subscribe to a plan, they automatically receive entitlements that grant access to these features.

<Info>
  Features and metered entitlements require Standard Billing (`billingVersion: 1`). If you are on
  a Legacy Billing plan, see [Billing Versions](/platform/billing/billing-versions) for details on
  the two models.
</Info>

## How it works

1. **Create features** for your product (via Platform UI)
2. **Link features to prices** within a plan
3. **Customers subscribe** and entitlements are provisioned automatically
4. **Check entitlements** via API to gate access in your application

## Feature types

<CardGroup cols={3}>
  <Card title="Boolean" icon="toggle-on">
    Simple on/off access. Either the customer has it or they don't.

    **Examples:** SSO enabled, Premium support, API access
  </Card>

  <Card title="Static" icon="sliders">
    Fixed capability with configuration values that define limits or quotas.

    **Examples:** 25 team seats, 100GB storage, 1000 API calls/month
  </Card>

  <Card title="Metered" icon="gauge">
    Usage-based access enforced against real consumption data. The customer's quota decrements as they use your product.

    **Examples:** 1M tokens/month, 100 API calls/day, 50GB storage
  </Card>
</CardGroup>

## Creating features

You can create features via the Platform UI or the API.

<Tabs>
  <Tab title="Platform UI">
    <Steps>
      <Step title="Navigate to your product">
        Go to **Products** and select the product you want to add features to.
      </Step>

      <Step title="Open the Features tab">
        Select the **Features** tab to view and manage features for this product.
      </Step>

      <Step title="Create a new feature">
        Select **New Feature** and fill in the details:

        * **Name**: Display name shown in the UI (e.g., "AI Tokens")
        * **Key**: Unique identifier used in API calls, lowercase with hyphens (e.g., `ai-tokens`)
        * **Type**: Boolean, Static, or Metered
      </Step>

      <Step title="Save the feature">
        Select **Save** to create the feature. It's now available to link to prices.
      </Step>
    </Steps>
  </Tab>

  <Tab title="API">
    ```bash cURL theme={null}
    curl -X POST "https://api.paygentic.io/v0/features" \
      -H "Authorization: Bearer sk_test_YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "key": "ai-tokens",
        "name": "AI Tokens",
        "type": "metered",
        "merchantId": "mer_abc123",
        "productId": "prod_xyz789"
      }'
    ```

    | Field        | Required | Description                                            |
    | ------------ | -------- | ------------------------------------------------------ |
    | `key`        | Yes      | Unique identifier, lowercase with hyphens              |
    | `name`       | Yes      | Display name                                           |
    | `type`       | No       | `boolean`, `static`, or `metered` (default: `boolean`) |
    | `merchantId` | Yes      | Your merchant ID                                       |
    | `productId`  | Yes      | The product this feature belongs to                    |
  </Tab>
</Tabs>

<Tip>
  Use descriptive, consistent keys for your features. They'll appear in API responses and your application code.
</Tip>

## Linking features to prices

Once you have features, link them to prices within your plans. This determines what entitlements customers receive when they subscribe.

<Steps>
  <Step title="Navigate to a plan">
    Go to **Products** > **Plans** and select the plan you want to configure.
  </Step>

  <Step title="Edit a price">
    Select a price within the plan to edit its configuration.
  </Step>

  <Step title="Select a feature">
    Select a feature from the dropdown to associate with this price.
  </Step>

  <Step title="Configure entitlement template">
    For Static features, define the configuration values that will be granted:

    ```json theme={null}
    {
      "maxUsers": 25,
      "storageGB": 100
    }
    ```

    For Metered features, define the usage limit:

    ```json theme={null}
    {
      "limit": 1000000
    }
    ```

    This grants the customer 1,000,000 units of usage per billing period, tracked against the billable metric linked to this price.
  </Step>
</Steps>

<Note>
  To add features to a plan without charging for them, create a fee with a price of 0. This lets you attach a feature for entitlement purposes while not billing the customer.
</Note>

## Automatic entitlement provisioning

When a customer subscribes to a plan, entitlements are automatically provisioned based on the features linked to that plan's prices. When the subscription is terminated, they are canceled. No manual intervention required.

<Note>
  Existing active subscriptions do not automatically pick up features you add to (or remove from) a plan after the fact. To converge them to the plan's current shape, use [Reconciling subscription features](/platform/billing/reconciling-subscription-features).
</Note>

## Checking entitlements via API

Use the API to check if a customer has access to specific features. This is how you gate functionality in your application.

<Info>
  **API Reference**: See the [List Entitlements API endpoint](/api-reference/entitlements/list-entitlements) for complete request and response schemas.
</Info>

### List customer entitlements

<CodeGroup>
  ```javascript JavaScript theme={null}
  async function checkEntitlements(customerId) {
    const url = new URL('https://api.paygentic.io/v1/entitlements');
    url.searchParams.set('customerId', customerId);
    // Optional filters:
    // url.searchParams.set('featureKey', 'sso-access');
    // url.searchParams.set('productId', 'prod_abc123');

    const response = await fetch(url, {
      headers: { 'Authorization': 'Bearer sk_test_YOUR_API_KEY' }
    });
    return response.json();
  }

  // Example: Check if customer has SSO access
  const entitlements = await checkEntitlements('cus_abc123');
  const hasSso = entitlements.data.some(
    e => e.featureKey === 'sso-access' && e.hasAccess
  );
  ```

  ```bash cURL theme={null}
  curl -X GET "https://api.paygentic.io/v1/entitlements?customerId=cus_abc123" \
    -H "Authorization: Bearer sk_test_YOUR_API_KEY"
  ```

  ```python Python theme={null}
  import requests

  def check_entitlements(customer_id):
      response = requests.get(
          "https://api.paygentic.io/v1/entitlements",
          params={"customerId": customer_id},
          headers={"Authorization": "Bearer sk_test_YOUR_API_KEY"}
      )
      return response.json()

  # Example: Check if customer has SSO access
  entitlements = check_entitlements("cus_abc123")
  has_sso = any(
      e["featureKey"] == "sso-access" and e["hasAccess"]
      for e in entitlements["data"]
  )
  ```
</CodeGroup>

### Response format

```json theme={null}
{
  "object": "list",
  "data": [
    {
      "hasAccess": true,
      "featureKey": "sso-access",
      "featureType": "boolean",
      "config": null,
      "entitlementId": "ent_abc123",
      "productId": "prod_xyz789",
      "activeFrom": "2024-01-01T00:00:00Z",
      "activeTo": null,
      "status": "active"
    },
    {
      "hasAccess": true,
      "featureKey": "storage-quota",
      "featureType": "static",
      "config": {
        "limitGB": 500
      },
      "entitlementId": "ent_def456",
      "productId": "prod_xyz789",
      "activeFrom": "2024-01-01T00:00:00Z",
      "activeTo": null,
      "status": "active"
    }
  ],
  "pagination": {
    "limit": 20,
    "offset": 0,
    "total": 2
  }
}
```

### Key response fields

| Field         | Description                                                          |
| ------------- | -------------------------------------------------------------------- |
| `hasAccess`   | Whether the customer currently has access to this feature            |
| `featureKey`  | The unique identifier for the feature                                |
| `featureType` | `boolean`, `static`, or `metered`                                    |
| `config`      | For static features, the configuration values (limits, quotas, etc.) |
| `status`      | Current status: `active`, `expired`, or `canceled`                   |

<Note>
  The list endpoint (`GET /v1/entitlements`) returns `hasAccess` but does **not** include `balance` or usage data. To get real-time balance and usage for a metered entitlement, use `GET /v1/entitlements/{entitlementId}`.
</Note>

## Metered features

Metered features enforce usage-based quotas against real consumption data. Instead of a static config value, the system tracks how much the customer has actually used and compares it against their granted limit.

### How metered entitlements work

1. You configure a feature with `type: metered` and link it to a price with a billable metric
2. The entitlement template defines the usage limit (e.g., `{ "limit": 1000000 }`)
3. As the customer sends [meter events](/platform/metering/meter-events), their usage accumulates
4. Your application checks the entitlement balance before allowing each operation

The grant engine reads aggregated meter data in real time, so `hasAccess` always reflects current consumption.

### Checking a metered entitlement balance

Use `GET /v1/entitlements/{entitlementId}` to get a real-time snapshot of a customer's usage and remaining quota. For metered entitlements, the response includes live balance and usage data in addition to the base entitlement fields.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://api.paygentic.io/v1/entitlements/ent_abc123" \
    -H "Authorization: Bearer sk_live_YOUR_API_KEY"
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    'https://api.paygentic.io/v1/entitlements/ent_abc123',
    { headers: { 'Authorization': 'Bearer sk_live_YOUR_API_KEY' } }
  );
  const entitlement = await response.json();

  if (!entitlement.hasAccess) {
    return { error: 'quota_exceeded', remaining: 0 };
  }

  // Proceed — customer has quota remaining
  console.log(`Remaining: ${entitlement.balance} units`);
  ```

  ```python Python theme={null}
  import requests

  response = requests.get(
      "https://api.paygentic.io/v1/entitlements/ent_abc123",
      headers={"Authorization": "Bearer sk_live_YOUR_API_KEY"},
  )
  entitlement = response.json()

  if not entitlement["hasAccess"]:
      raise Exception("Quota exceeded")
  ```
</CodeGroup>

### Balance response

```json theme={null}
{
  "object": "entitlement",
  "id": "ent_abc123",
  "customerId": "cus_xyz789",
  "featureId": "feat_789",
  "featureKey": "ai-tokens",
  "featureType": "metered",
  "subscriptionId": "sub_abc",
  "status": "active",
  "activeFrom": "2026-01-01T00:00:00Z",
  "activeTo": null,
  "hasAccess": true,
  "metadata": {},
  "balance": 450000,
  "usageInPeriod": 550000,
  "overage": 0,
  "currentPeriodStart": "2026-02-01T00:00:00Z",
  "currentPeriodEnd": "2026-03-01T00:00:00Z"
}
```

| Field                                     | Description                                                                                 |
| ----------------------------------------- | ------------------------------------------------------------------------------------------- |
| `id`                                      | The entitlement ID                                                                          |
| `featureKey`                              | The unique identifier for the feature                                                       |
| `featureType`                             | Always `"metered"` for metered features                                                     |
| `status`                                  | Entitlement status: `active`, `expired`, or `canceled`                                      |
| `hasAccess`                               | `true` if the customer still has quota, or if the entitlement is configured as a soft limit |
| `balance`                                 | Units remaining in the current period                                                       |
| `usageInPeriod`                           | Units consumed so far this period                                                           |
| `overage`                                 | Units consumed beyond the granted limit (if soft limit)                                     |
| `currentPeriodStart` / `currentPeriodEnd` | The active billing or entitlement period                                                    |

### Hard limits vs soft limits

By default, `hasAccess` becomes `false` once `balance` reaches zero. You can configure an entitlement as a **soft limit**, which allows usage to continue beyond the granted quota (generating overage) while still returning `hasAccess: true`.

Use soft limits when you want to track overages for post-period billing or alerting, without hard-blocking the customer at the quota boundary.

### Required setup

For a metered feature to work, your price must be linked to a [billable metric](/platform/pricing/billable-metrics) that has `eventType`, `valueProperty`, and `aggregation` configured. The grant engine uses these fields to query the meter data and calculate current usage.

See [Meter Events](/platform/metering/meter-events) for how to instrument your application to send usage data.

## Use cases

### Feature gating

Check `hasAccess` before enabling premium features in your application:

```javascript theme={null}
const entitlements = await checkEntitlements(customerId);
const ssoEntitlement = entitlements.data.find(e => e.featureKey === 'sso-access');

if (ssoEntitlement?.hasAccess) {
  // Enable SSO login option
  showSsoButton();
} else {
  // Show upgrade prompt
  showUpgradePrompt('SSO is available on Professional plans and above');
}
```

### Enforcing static limits

Read `config` values for static features to enforce limits:

```javascript theme={null}
const entitlements = await checkEntitlements(customerId);
const storageEntitlement = entitlements.data.find(e => e.featureKey === 'storage-quota');

if (storageEntitlement?.hasAccess) {
  const limitGB = storageEntitlement.config.limitGB;
  const currentUsageGB = await getCurrentStorageUsage(customerId);

  if (currentUsageGB >= limitGB) {
    throw new Error(`Storage limit of ${limitGB}GB reached`);
  }
}
```

### Enforcing metered quotas

Check the balance endpoint before allowing consumption:

```javascript theme={null}
async function canProcessRequest(customerId, entitlementId) {
  const response = await fetch(
    `https://api.paygentic.io/v1/entitlements/${entitlementId}`,
    { headers: { 'Authorization': `Bearer ${API_KEY}` } }
  );
  const { hasAccess, balance } = await response.json();
  return { allowed: hasAccess, remaining: balance };
}

// Before processing an AI inference request
const { allowed, remaining } = await canProcessRequest(customerId, entitlementId);
if (!allowed) {
  return res.status(429).json({ error: 'Token quota exceeded' });
}

// Process the request, then send a meter event
await processInference(request);
await sendMeterEvent({ type: 'ai.inference', subject: customerId, data: { tokens: 1500 } });
```

## Next steps

* [Meter Events](/platform/metering/meter-events) — Send usage data that powers metered entitlements
* [Billable Metrics](/platform/pricing/billable-metrics) — Configure how meter events are aggregated
* [Customer Lifecycle](/platform/billing/customer-lifecycle) — Creating subscriptions that provision entitlements
* [Plans](/platform/pricing/plans) — Configure pricing plans
* [Prices](/platform/pricing/prices) — Set up prices with features
