Browse the library
Documentation libraryLicensing
License plans and entitlements
A license plan (policy) is the rulebook every key carries: its term, how many machines it runs on, grace, trial, move rules and the entitlements your software checks with `has()`. Define a plan once and every license made from it follows the same rules.
8 min read
How plans work
Each app has its own list of plans. A new app starts with one plan named Standard: 30 days, 1 machine, set as the default. Edit it, or add plans such as "Pro", "Lifetime" or "Free".
A license plan is not your workspace's Basic or Premium subscription. License plans are what you sell to your customers; the subscription sets your own limits, see Basic, Premium and limits.
When a key is created, some plan values are copied onto the license and the rest are read from the plan every time they are needed:
| Value | How it applies |
|---|---|
| Term, machines | Copied onto the license when the key is created. Editing the plan later does not change keys that already exist |
| Entitlements, grace, notice, sessions at once, move rules, silent machines | Read from the plan at every sign-in, heartbeat or scheduled sweep, so changes also reach keys you already sold, within seconds |
Create or edit a plan
- Open the app and go to Licensing & releases › Sign-in & license plans.
- Under License plans, click Add plan. To change an existing plan, click its card.
- Fill in the groups below, then click Save plan.
Each plan card sums up the term (or Lifetime), machines, trial days, grace days and granted entitlements. The default plan carries a Default label.
Basics
- Name: up to 64 characters.
- Term (days): empty means lifetime, up to 10 years. The term starts at first activation, not when the key is created, so unsold keys sitting in stock do not lose days.
- Machines: how many devices one key can be bound to at once, at least 1 and no more than your subscription's devices-per-key limit. A machine beyond this number is refused with
device_limit. - Default for new keys and free accounts: an app has one default plan. Ticking it on another plan clears it from the old one.
The default plan is used in three places: the Create license keys sheet preselects it in the Plan field (the field only shows when the app has two or more plans); the Seller API uses it when a key is created without a policyId; and in the Account or Account + license key sign-in modes, people who register without a key get a free license on this plan.
Trial
- Trial days: 1 to 90. Leave it empty when the plan is not a trial plan.
- After the trial: another plan of the same app (usually a free tier), or Stop. A plan cannot fall back to itself.
Device transfers
- Per 30 days: 0 to 100. Empty means 2.
- Hours between transfers: 0 to 720. Empty means 24 hours.
- Seller, Buyer, Reseller: who may move a key to another machine. With none ticked, the seller and the buyer may.
Buyers move from inside your software (transfer() in the SDK) or from the customer portal. Each move counts against the allowance and the cooldown: past the limit they get transfer_limited, and when buyers are not allowed at all they get transfer_not_allowed. Resellers can reset devices on keys they sold only when the plan has Reseller ticked. Machines you remove yourself in the dashboard are recorded but never count against the buyer's allowance.
Time
- Grace after the end (days): 0 to 30, default 0. An expired license keeps working for this many days and your software sees
state: "grace". After that the license stops: machines bound to the key getlicense_expired, the license turns expired and webhooks receivelicense.expired. - Notice before the end (days): 0 to 60, default 7. Inside this window your software sees
state: "expiring". Once per term, alicense.expiringevent fires and the buyer gets one email if the license has an email address (with a portal link when the customer portal is on). Set 0 to turn it off. After an Extend, the new term gets its own notice. - Silent machine frees its seat after (hours): empty is 72 hours, 0 is never, at most 8,760 hours (one year). A machine that has not talked to the server for longer is marked inactive and its seat goes back to the pool. If it returns within 7 days and the license has a free seat, it takes a seat back on its own and that does not count as a move; after 7 days it is removed from the license for good.
- Sessions at once: how many places one key can run in at the same time. Empty means no cap. With a cap, a new sign-in on the same machine replaces that machine's old session, live sessions on other machines count, and going over returns
session_limit.
Settings not on the form yet
Each plan has three more settings that the engine reads, but the plan form in the dashboard has no fields for them yet. Plans created in the dashboard use the defaults:
| Setting | Default | What it does |
|---|---|---|
| Offline TTL | Empty, 7 days | How long a signed offline token stays valid, from 1 hour to 90 days and never past the license end date. Setting it needs Premium, otherwise tokens last 7 days. No token is issued while Online-Only Mode is on |
| Plan heartbeat | Empty, off | 60 seconds to 1 day. A machine silent for more than two intervals is removed from the license at the next sign-in on that key |
| Fingerprint threshold | 3 | 1 to 8. How many hardware components must match for a machine to count as the same one after a reinstall or a part swap, so it reuses its old seat |
The plan heartbeat is not the Short-Lived Session Lease switch in Advanced Protection: that switch decides how fast sessions lapse, the plan heartbeat decides when a machine gives up its seat. Offline tokens are covered in Offline licenses.
Machine-bound trials
Trials exist only in the License key sign-in mode. When at least one plan has Trial days, init() returns app.trial: true and your software can call trial():
- The trial runs on the default plan if it has trial days, otherwise on the oldest plan that has them. Keeping a single plan with trial days makes this easy to reason about.
- Each machine gets one trial per app, recognised by the device rather than the IP. Calling
trial()again on the same machine returns the trial already running. - The trial gets the trial plan's machines, entitlements and rules. Its length is fixed when it starts.
- When it ends: with no fallback the answer is
trial_expired. With a fallback, the license moves to that plan (its machines, entitlements and term, counted from the switch) and the answer carriestrial.fallback: true. - Activating a real key on that machine converts the trial; after that
trial()returnstrial_used. - An IP with a high risk score in Protect 24/7 cannot start a trial (
trial_denied).
const { app } = await clavis.init();
if (app.trial) {
const grant = await clavis.trial();
if (grant.trial?.fallback) {
// The trial is over: the software now runs on the fallback plan
}
}
Entitlements
Entitlements are flags such as export_pdf or batch_mode that a plan grants to its licenses. The server sends them in the signed answer, so you ship one build and let the plan decide who gets which feature.
Declare entitlements on the app
- Go to Licensing & releases › Sign-in & license plans and find the Features card.
- Type a Display name (for example "Export PDF") and a key (for example
export_pdf), then click +. - A key starts with a lowercase letter and holds only lowercase letters, digits and underscores, at least 2 characters; the field takes up to 40. Leave the key empty and the dashboard derives it from the display name.
An app can declare up to 64 entitlements (and a plan can grant up to 64); a duplicate key is refused.
Grant entitlements in a plan
Open the plan, tick the entitlements it should grant in the Features group and click Save plan. Only declared entitlements appear there; with none declared, the form asks you to declare them first. Granted entitlements show on the plan card and in each license's details under Licensing & releases › Licenses (the Features row).
Delete an entitlement
Click the bin icon (Delete entitlement) and confirm with Delete. The entitlement leaves the app and every plan at the same moment, and running software stops receiving it at its next sign-in or heartbeat. Keys are a contract between the dashboard and your code, so renaming one means deleting it, creating the new key and shipping a build that reads it.
Read entitlements in your software
The JavaScript SDK keeps the entitlements of the last verified answer. clavis.has("export_pdf") returns true or false, and clavis.entitlements returns the keys as an array. The list arrives with activate(), login(), register(), redeem() and trial() (as grant.entitlements), and every heartbeat() refreshes it, so an entitlement you add to a plan reaches running machines on their next beat.
import { ClavisClient } from "@clavisauh/sdk";
const clavis = new ClavisClient({ baseUrl, appId: "app_…", appPublicKey, device });
const grant = await clavis.activate(licenseKey);
console.log(grant.entitlements); // e.g. ["export_pdf"]
if (clavis.has("export_pdf")) showExportMenu();
// Warn the user before the software stops
if (grant.license.state === "expiring") showRenewBanner(grant.license.expiresAt);
if (grant.license.state === "grace") showGraceWarning();
setInterval(async () => {
await clavis.heartbeat();
toggleExportMenu(clavis.has("export_pdf"));
}, 30 * 60_000);
Cases to plan for:
- Before the first successful sign-in,
has()returnsfalsefor everything andentitlementsis empty. - Offline tokens do not carry entitlements. If your software starts without a network and relies only on
verifyOffline(),has()returnsfalse, so decide on purpose which features work offline. - A license with no plan gets an empty list.
Limits and caveats
- Changing a plan's Term (days) or Machines does not change keys already created. For sold keys, use Extend on the Licenses page or the Seller API.
- The Create license keys sheet lets you pick a different Duration and Devices per key for that batch only.
- There is no way to delete a plan today. Rename a plan you no longer use and clear its default mark.
- Resellers can only sell the plans you grant them, see Resellers and credit.