Skip to main content

Model plans and entitlements

Before you sell anything, you decide what your product can gate and what each tier unlocks. In Licet that's two concepts:

  • Entitlement definitions — the vocabulary of things your application understands. Two kinds: a feature (boolean, e.g. white_label) or a limit (numeric, e.g. seats).
  • Plans — named tiers (Free, Pro, Enterprise…) that assign values to those entitlements and map to a Stripe price for checkout.

The names you choose here are the exact strings your product checks with the SDK — license.Has("white_label"), license.Limit("seats") — so treat them like an API you're committing to.

All the endpoints below need a key with the admin scope; the portal does the same things interactively.

Define the vocabulary

curl -X POST https://api.licet.app/v1/apps/{app_id}/entitlement-defs \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{ "key": "seats", "kind": "limit", "display_name": "Seats", "default": "1" }'
{
"id": "ent_…",
"key": "seats",
"kind": "limit",
"display_name": "Seats",
"default": "1"
}
  • key must match ^[a-z][a-z0-9_]*$ — lowercase, digits, underscores. It can't be renamed later without breaking installs that check it, so choose carefully.
  • kind is feature (default must be true/false) or limit (default must be an integer).
  • The default applies wherever a plan doesn't set its own value.

Deleting a definition (DELETE …/entitlement-defs/{ent_id}) also removes any values plans had for it — but licences already issued keep their snapshot (more on snapshots below).

Create plans

curl -X POST https://api.licet.app/v1/apps/{app_id}/plans \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"name": "Pro",
"code": "pro",
"entitlements": [
{ "key": "white_label", "value": "true" },
{ "key": "seats", "value": "10" }
]
}'
{
"id": "pln_…",
"name": "Pro",
"code": "pro",
"status": "active",
"active_licences": 0,
"entitlements": [{ "entitlement_def_id": "ent_…", "value": "true" }]
}
  • Every entitlement key must already exist as a definition.
  • code is unique per application and purely vendor-facing — everything in the platform (prices, licences, checkout) binds to the id.
  • To sell the plan, map it to a Stripe price in the portal (Payments → Plan prices) — see Sell a plan with Checkout.

Editing a plan

PATCH /v1/apps/{app_id}/plans/{plan_id} takes the same body. One rule worth underlining: when you include entitlements, the list is the complete set of overrides — values you list are applied, values you omit revert to their definition defaults, and an empty list [] clears every override. Omit the field entirely to leave entitlements untouched.

Snapshots: what existing customers experience

A licence captures its entitlements at issue time and keeps that snapshot. Editing a plan, or deleting a definition, changes what future buyers get — it does not rewrite licences already in the field. If you need to change what an existing customer has, act on their licence, not the plan.

This is deliberate: a plan edit can't accidentally downgrade thousands of paying customers, and a licence always means what it meant when it was sold.

Retiring a plan

Plans are never deleted — they're archived:

curl -X POST https://api.licet.app/v1/apps/{app_id}/plans/{plan_id}/archive \
-H "Authorization: Bearer sk_live_…"

Archiving does three things:

  1. The plan's status becomes retired — new licences can't be issued against it, and checkout for it stops.
  2. Every active licence on the plan is suspended (not revoked), each emitting a licence.suspended webhook with reason: "plan_archived".
  3. The response tells you how many licences that touched ("suspended_licences": …).

POST …/plans/{plan_id}/restore brings the plan back — but deliberately does not reactivate the suspended licences. Reactivation is a per-licence decision (POST /v1/licences/{id}/reactivate), so a plan restored for one customer doesn't silently re-enable a thousand others.

If archiving-suspends is not what you want — say you're sunsetting a tier but existing customers keep it for life — leave the plan active and simply remove its price mapping so it can't be bought.

Naming advice

  • Name entitlements after capabilities, not tiers: api_access, not pro_features. Tiers change; capabilities don't.
  • Prefer a limit over a feature when there's any chance of "how many" mattering later — seats: 1 today beats migrating has_seats tomorrow.
  • Keep the set small. Every key is a string your product checks forever.