# Entitlements

> Features and limits a brand grants through its products, resolved for each customer from what they've bought. Ownrecord says what a customer may use; your product enforces it.

An **entitlement** is a capability or limit a customer has because of what they've bought, such as `feature.export` or `seats.max = 10`. Your product asks Ownrecord what a customer is entitled to, instead of keeping its own copy of each customer's plan.

## Features

Each brand defines its **features**. A feature has:

- a `key`, such as `seats.max`;
- a `type`: `boolean` (on or off), `limit` (a number) or `enum` (one of a set of values);
- a rule for **combining** values when a customer gets the same feature from several sources;
- a default value;
- for limits, whether it's **per unit**: the value a product grants is multiplied by the quantity, so a plan granting 5 projects per seat gives a customer with 3 seats 15 projects.

**Products** grant features: each product lists feature keys and the value it grants. A product marked **lifetime entitlements** grants them for good when it's bought once, as with a lifetime licence; otherwise only subscriptions to it grant them, while they last.

Create features with [`features.create`](https://ownrecord.io/docs/reference/features-create.md) and grant them in [`products.create`](https://ownrecord.io/docs/reference/products-create.md) or [`products.update`](https://ownrecord.io/docs/reference/products-update.md), or in the console under **Catalog**. Test and live mode have separate features and products.

## How a customer's entitlements are resolved

Ownrecord combines these sources, using each feature's combine rule:

1. **Manual grants**: overrides set by your support team, with a reason and an optional expiry date.
2. **Subscriptions** that are `active` or `trialing` (a trial grants the plan in full), `past_due` ones until the grace period ends, and `paused` ones only if the brand keeps entitlements during a pause (off by default). A subscription item with a quantity of 0 grants nothing.
3. **One-time purchases** of products with lifetime entitlements, while the payment stands (a fully refunded payment no longer counts).
4. **Feature defaults**, for every feature nothing else grants.

| Combine rule | Fits                    | Result                                                                                                                    |
| ------------ | ----------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `max`        | limits                  | The highest value. An override can raise a limit but not lower it.                                                        |
| `sum`        | limits                  | All values added up, so an override or an add-on adds to the plan.                                                        |
| `or`         | booleans                | On if any source switches it on. An override can switch a feature on but not off.                                         |
| `first`      | booleans, limits, enums | The first source in the order above decides: the newest override, then the oldest subscription, then the oldest purchase. |

The result has one value for every feature of the brand, and lists the sources each value came from.

## When entitlements change

Ownrecord stores each customer's entitlements and recomputes them **within one job cycle** of every change: a subscription created, changed, paused or canceled, a lifetime purchase, an override granted or removed, a feature's combine rule, default or per-unit setting changed, or a product's entitlements changed. Time-based changes are picked up the same way: an override reaching its expiry date, or the end of a past-due grace period, is applied by a sweep that runs every minute.

A customer on a [test clock](https://ownrecord.io/docs/concepts/test-clocks.md) lives at the clock's time instead: their entitlements are resolved at the clock's time (`computedAt` is a time on the clock), an override's expiry date is a time on the clock, and time-based changes happen when you advance the clock, not when real time passes.

When a value changes, the brand's webhook endpoints receive **`customer.entitlements.updated`**. Its `data.object` is the customer's full set, as the API returns it, plus `changes`: one entry per feature whose value changed, with `before` and `after`. `previous_attributes.values` has the previous set.

```json
{
  "type": "customer.entitlements.updated",
  "data": {
    "object": {
      "object": "customer_entitlements",
      "customerId": "cus_01m4ty3c2b5qx7mhd9k8w6s0aa",
      "values": { "feature.export": true, "seats.max": 20 },
      "sources": [
        {
          "type": "override",
          "ref": "ceo_01m4ty3c2b5qx7mhd9k8w6s0b2",
          "featureKey": "seats.max",
          "value": 20
        }
      ],
      "computedAt": "2026-10-14T10:00:05.000Z",
      "changes": [{ "featureKey": "seats.max", "before": 15, "after": 20 }]
    }
  },
  "previous_attributes": { "values": { "feature.export": true, "seats.max": 15 } }
}
```

A customer who only has defaults hasn't been materialised yet and gets no event when a default changes; the `feature.updated` event tells you that.

## Reading entitlements

- [`customers.entitlements.get`](https://ownrecord.io/docs/reference/customers-entitlements-get.md) (`GET /customers/{id}/entitlements`) returns `values`, `sources` and `computedAt`.
- [`entitlements.check`](https://ownrecord.io/docs/reference/entitlements-check.md) (`GET /entitlements/check?customer=…&feature=…`) returns one feature's `value`, and `enabled` (true for `true` or a limit above 0). Use it for the checks your product makes on every request.

Both read the stored set. While a recompute is pending (a change just happened or an override just expired), they resolve the values from the sources instead, so a read never returns values known to be out of date.

The TypeScript SDK caches each customer's entitlements in memory for 60 seconds and drops them when the webhook arrives:

```ts
import { createEntitlements } from "@ownrecord/sdk";

const entitlements = createEntitlements({ apiKey: process.env.OWNRECORD_API_KEY });

if (await entitlements.has(customerId, "feature.export")) {
  // …
}
const seats = await entitlements.limit(customerId, "seats.max");

// In your webhook route, with the raw body: verifies the signature, and on customer.entitlements.updated
// forgets that customer's cached entitlements so the next check reads the new values.
const event = await entitlements.handleWebhook(rawBody, request.headers.get("Ownrecord-Signature"), secret);
```

`has` fails closed: a missing feature, `false`, 0 or an enum value is "not enabled". `limit` returns 0 when the value isn't a whole number. Compare enum values with `entitlements.value(customerId, "support.level")`.

## Overrides

Support can grant a customer a value of one feature, with a reason and an optional expiry: extra seats while a team migrates, or a feature switched on for a beta tester. In the console, open the customer and use **Entitlements → Grant override**; through the API, call [`customers.entitlement_overrides.create`](https://ownrecord.io/docs/reference/customers-entitlement-overrides-create.md).

- An override is one more source and merges by the feature's combine rule (see the table above).
- It applies until `expiresAt`, or until it's deleted with [`customers.entitlement_overrides.delete`](https://ownrecord.io/docs/reference/customers-entitlement-overrides-delete.md), which also takes a reason.
- [`customers.entitlement_overrides.list`](https://ownrecord.io/docs/reference/customers-entitlement-overrides-list.md) lists a customer's overrides, including expired ones.
- A customer can have up to 100 active overrides. Granting and removing them needs `customers:write` (owners, admins and support), and both are in the audit log.

## You enforce, Ownrecord informs

Entitlements are information. Ownrecord doesn't deliver your service or block your users; your product checks entitlements and decides what to allow.

Entitlements aren't permissions. [Roles](https://ownrecord.io/docs/concepts/account-model.md#members-and-roles) control what your team members may do in Ownrecord. Entitlements describe what your customers may do in your product.

Ownrecord uses entitlements for its own plans too: the limits of your Ownrecord plan, such as how many brands you can create, are entitlements of the Ownrecord brand.
