Editions & entitlements
To sell your product in editions, sell each edition as a license model. Each model lists what its keys grant beyond the base product: its entitlements.
An entitlement is a name with an on/off flag, a number or a text value, such as
edition: "pro" or seats: 5. Every lease carries the key's entitlements in its
ent claim, so your app can tell the editions apart offline.
For instance, Base and Pro are two perpetual models, one granting edition: "base" and the
other edition: "pro". The device limit is a separate term, so a Pro model for studios can
grant the same edition: "pro" with more devices.
Read entitlements in your app
const status = await license.status();
if (status.state === "licensed" || status.state === "trial") { // Read offline from the lease. Absent when the lease grants nothing. const pro = status.entitlements?.edition === "pro"; const seats = typeof status.entitlements?.seats === "number" ? status.entitlements.seats : 1; enableFeatures({ pro, seats });} -
entitlementsis present on alicensedortrialstatus when the lease grants at least one, and the object is frozen. - Treat a missing name as not granted: a key sold before its model had any entitlements carries none.
- Values that are not a flag, a number or a text value are dropped rather than invalidating the license.
Check an entitlement
// Read offline from the stored lease: no request, no write.if (await license.has("cloud_sync")) enableCloudSync();
// Rejects with MissingEntitlementError (its entitlement is "export") when not granted.await license.require("export"); -
has(code)answers whether the license grants the entitlementcode: a flag that is on, a number that is not zero (so-1for unlimited is granted), or a text value that is not empty. The code is matched exactly, case included. -
It reads the stored lease, as
status()would, with no request and no write: a license that does not license this build grants nothing, and a running trial grants what its trial lease does. -
require(code)rejects withMissingEntitlementError, naming the code, whenhaswould be false.
Every KeyGrant SDK has both, in its own language's form: see SDKs.
Define each entitlement once
Each product lists its entitlements on its Entitlements tab, one for each thing your app checks. Each entitlement has:
The name your app reads, such as cloud_sync. A new code may not match another entitlement's ignoring case.
The name people read, such as Cloud sync.
Optional, one line.
On/off, Number or Text.
The value it takes when you add it to a license model, a key or the trial.
- License models, upgrade offers, a key's own entitlements and the product's Trial entitlements pick from these.
- The tab lists, for each entitlement, the license models, upgrade offers and trial that use it.
- A product holds at most 16 entitlements.
Rename or remove an entitlement
- An entitlement's name and description change freely.
- Changing its code renames it in the product's license models, upgrade offers and trial, but never on a key already issued: your shipped app checks the code, and each key keeps the one it was given.
- A running device trial takes the new code the next time the app starts its trial.
- An entitlement is removed from the tab, or its type changed, only while no license model, upgrade offer or the trial uses it.
Where a key's entitlements come from
- From its model: fixed when the key is sold, like its other terms. Editing the model later affects only keys sold after the edit.
- Set at issue: Set entitlements for this key on the Issue
license form, or
entitlementson POST /v1/licenses, replaces the model's set entirely.nullor{}grants none. - Changed later: open the key, and choose Edit beside
Entitlements in its Signed lease card. The whole set is replaced, the
model is not changed, and the app picks up the new set at its next check-in, or at once with
refresh(). - For a trial: the product's Trial entitlements, on its Settings tab, are what a device trial grants (see Trials). A trial key whose model grants none gets a copy of them when the key is created, so a later change to the setting does not reach it.
- A key a checkout creates on a trial model's price is a paid key, not a trial key, and grants only the model's own.
- For a key sold under no model: those of the product's oldest model of its type, or none when the product has no model of that type. A trial key that would get none takes the product's Trial entitlements instead.
The lease of a key with no entitlements carries ent: {}.
Sell an edition upgrade
To sell an upgrade from one edition to another, such as Pro for the difference in price:
- Turn on Sell as an upgrade on a perpetual license model. The switch appears only while a Stripe or Paddle account is linked.
- Choose Entitlements under What it upgrades.
-
Give the set a key must hold (
edition: "base") and the set it gets in their place (edition: "pro").
A paid checkout for the model then changes the entitlements of the key the customer already holds, as a
version upgrade adds versions. Call
refresh() after the checkout and the app reads the new edition at once.
- Which key: a perpetual or timed key of the product, in the checkout's mode, currently valid, and holding the first set: the key the checkout names, else the one key at the buyer's address that can take it (see How KeyGrant finds the key).
- What it keeps: its other entitlements, its devices and its versions.
- It never creates a key.
When an edition sale is held
An edition sale is held when:
- It finds no key that can take it: neither the key the checkout named nor any key at the buyer's address.
- The address holds more than one key that can take it, or more than 20 keys that might, and the checkout named none of them.
- The key it finds already holds the set it gives.
- It buys a different number at once than that key has seats. A checkout that does not say how many were bought counts as one.
- KeyGrant cannot tell its line in the checkout: it was bought with other items, or Stripe would not give the checkout's lines.
A held sale waits for you to attach it to a key or refund it: see Held sales.
Refunds and emails
- A full refund (while Refunds revoke keys is on) or a lost dispute puts the key's entitlements back, unless they have changed since, and the key keeps working either way (see Refunds and disputes).
- While KeyGrant sends your customer mail, it emails the customer that their key has the new edition: the Edition upgrade email on License delivery, which you can turn off.
- If you send your own mail, the
license.upgradedwebhook is where you tell them.
The rules
A set of entitlements, on a model, a key or the product's Trial entitlements, must follow these rules:
| Rule | Limit |
|---|---|
| Entitlements in a set | At most 16. |
| A name | 1 to 32 characters, in Unicode composed form (NFC), and never __proto__. |
| A value | true or false, a finite number, or a text value of at most 64 characters. |
| A name or a text value | No unpaired surrogate. |
| Each entitlement | Defined on the product, with a value of its type. |
A set that breaks them is rejected when you save it, not at activation. The API answers:
| Error | When |
|---|---|
400 invalid-request | The set breaks the first four rules. |
400 unknown-entitlement | A name the product has not defined. entitlement names it. |
400 entitlement-type | A value of another type. entitlement names it. |
- Lengths count UTF-16 units, so a character outside the Basic Multilingual Plane counts twice.
- An entitlement a key already holds is kept as it is when you save the key, even after it is removed from the product's Entitlements tab or changed there.
Products that existed before the catalog
- Each product's entitlements were first made from the names it already used, typed by their values and named from their codes, for you to rename.
- A product that used more than 16 names keeps them all, but takes no new entitlement until you remove some.
-
Names that differ only in case (
cloudandCloud) are each an entitlement: remove the one nothing uses, or keep both.