# Product Entitlements

Product entitlements are stable access-right identifiers attached to products.
An active customer subscription grants the union of entitlements on the
products in its open timeline segment.

Use the public API at
[`https://api.ledgerbee.com/api/v1`](https://api.ledgerbee.com/api/v1) with an
`x-api-key` header. Reading requires `products-read`. Creating, updating,
deleting, and changing product bindings require `products-write`. The generated
[API Reference](/api) defines each request and response schema.

## Define an entitlement

`POST https://api.ledgerbee.com/api/v1/entitlements` creates an externally
managed entitlement. The optional `key` is a case-insensitive, tenant-unique
integration identifier. Reuse the same key in retries and reconciliation jobs.

```json
{
  "name": "Advanced reports",
  "key": "advanced-reports"
}
```

The full CRUD surface (list, read, update, delete) is documented in the
[API Reference](/api). Deleting requires the entitlement to be unbound from
every product first.

Reads return every entitlement in the tenant so product bindings and webhook
`entitlementIds` always resolve. Each response carries `managedExternally` and
`externalSource`. Only rows with `externalSource` `"api"` can be updated or
deleted through the public API; other rows are managed inside LedgerBee and
are read-only here.

## Attach entitlements to products

Pass `entitlementIds` when creating or updating a product. The array replaces
the public API-owned subset of that product's bindings. Bindings managed inside
LedgerBee are preserved.

```json
{
  "name": "Pro plan",
  "productNumber": "PRO",
  "productGroupId": "16fbe7dd-bc92-4ef7-953f-0f4d520e7189",
  "entitlementIds": ["69777e2d-9c96-4fc6-9848-d5fe7d8da437"]
}
```

Product detail responses include entitlement references. Product list items do
not include them. Fetch product detail when the binding set is needed.

## Read grants and synchronize access

`GET https://api.ledgerbee.com/api/v1/subscriptions/{id}` includes the current
`entitlements` union. There is no public subscription-preview endpoint.

Use the access-boundary topics in the subscription webhook topic table. Grant
or replace access when a subscription starts, transitions, resumes, or ends its
trial. Treat assignment as advance notice because a future-dated assignment is
not active yet. Revoke access on an effective pause or finalized cancellation;
both payloads carry an empty `entitlementIds` array.

See [Subscription webhooks](/guides/subscriptions/webhooks) for delivery,
ordering, signature verification, and retry guidance.

## Identifier and lifecycle contract

No bearer token or redeemable entitlement token is minted. LedgerBee delivers
entitlement UUIDs in API responses and webhooks. Store the UUID as the durable
identity. Use `key` for idempotent catalogue reconciliation and `name` only for
display.

Changing a product binding changes the union returned by the next subscription
read and the next access-boundary event. Access begins only when a
granting-status segment is open. Pausing or final churn revokes the set in its
access-boundary webhook. If webhook delivery is delayed, process retries
idempotently by event id and reconcile against the subscription detail
endpoint.

## Support matrix

| Operation                                     | Public API-owned entitlement | LedgerBee-managed entitlement |
| --------------------------------------------- | ---------------------------: | ----------------------------: |
| Read entitlement                              |                          Yes |                           Yes |
| Update or delete entitlement                  |                          Yes |                            No |
| Replace product bindings through public API   |                     Replaced |                     Preserved |
| Replace product bindings in the LedgerBee app |                    Preserved |                      Replaced |
| Delete a bound entitlement                    |                           No |                            No |

Deleting a product removes all of that product's entitlement bindings. It does
not delete shared entitlement records.

## Errors and failure states

| Condition                                             | What you observe                        | Action                                                     |
| ----------------------------------------------------- | --------------------------------------- | ---------------------------------------------------------- |
| Entitlement id is absent from the tenant              | `ENTITLEMENT_NOT_FOUND`                 | Refresh the entitlement list and verify the id.            |
| Name is already used                                  | `ENTITLEMENT_NAME_EXISTS`               | Choose another name or use the existing entitlement.       |
| Case-insensitive key is already used                  | `ENTITLEMENT_KEY_EXISTS`                | Re-read by key and continue with the existing entitlement. |
| Products still reference the entitlement              | `ENTITLEMENT_IN_USE`                    | Remove the product bindings before deletion.               |
| Public write targets a LedgerBee-managed row          | `ENTITLEMENT_MANAGED_INTERNALLY`        | Leave the row unchanged or ask an operator to change it.   |
| Write targets a row owned by another system           | `ENTITLEMENT_MANAGED_EXTERNALLY`        | Change it through the owning integration.                  |

All errors use the standard public [error envelope](/guides/errors). Safe
network retries should use an [idempotency key](/guides/idempotency).
