Browse the library
Documentation libraryLicensing
Sign-in modes and trials
Each app picks one way for people to get into your software: enter a license key, sign in with an account, or sign up for a free account and upgrade it with a key. This guide covers each mode, how to offer a machine-bound trial, and the checks every sign-in passes first, such as maintenance mode and the minimum version.
8 min read
The three sign-in modes
The sign-in mode decides which calls the server accepts from your software. A new app always starts in License key mode.
| Mode | Name in the dashboard | Accepted calls | Use it when |
|---|---|---|---|
key | License key | activate, trial | You sell keys and users need no account |
account | Account | register, login | Your software has its own accounts, a key is optional at sign-up |
account_key | Account + license key | register, login, redeem | Sign-up is free and a key unlocks the paid edition |
- License key: the user pastes a key and your software calls
activate(key). This is also the only mode with a machine-bound trial throughtrial(). - Account: users register with a username (3 to 64 characters: unaccented letters, digits,
_,.,-) and a password (8 to 128 characters); the email is optional. With a key, the account is tied to that key's license. Without one, the account starts on the app's default plan. - Account + license key: the same as Account, plus a
redeem(key)call that upgrades a signed-in account to a paid license while it keeps its username, password and current machine.
Changing the mode in the dashboard
- Open the app and go to Licensing & releases › Sign-in & license plans.
- Under Sign-in mode, click the card for the mode you want. The active card is highlighted, and each card lists the calls it accepts.
- The dashboard saves right away and shows "Saved". The server applies the new mode almost at once, within about 30 seconds at most.
Changing the mode deletes no license, account or session. It only decides which sign-in calls the server accepts from then on.
Calling the wrong mode: mode_not_allowed
If your software sends a call the current mode does not accept, for example activate while the app is in Account mode, the server answers HTTP 403 with the code mode_not_allowed and a message naming the mode in use. This check runs before the other sign-in checks, such as maintenance, version, key or password. To avoid it, call init() at startup and build the sign-in form from app.loginMode instead of hard-coding it in the build.
Machine-bound trials
A trial lets people run your software without a key for a number of days you choose. Each machine gets one trial per app. The machine is recognised by the fingerprint the SDK sends, never by IP, so switching networks or using a VPN does not open another trial.
Turning the trial on
- Go to Licensing & releases › Sign-in & license plans and select License key mode. Trials only exist in this mode.
- Under License plans, click the plan to use for trials, or click Add plan.
- In the Trial section, enter Trial days (1 to 90).
- In After the trial, choose another plan of the app as the fallback, or Stop.
- Click Save plan. The plan card now shows "N-day trial".
The trial takes its number of machines and its entitlements from that plan. If several plans have trial days, the server uses the default plan first, then the oldest one.
How a trial behaves
init()returnsapp.trial = truewhen the app is in License key mode and at least one plan has trial days. Show a "Try it" button only when this istrue.- The first
trial()call on a machine creates an internal license for it. The user never sees its key. In Licensing & releases › Licenses it shows with aTRIAL-prefix and the note "Trial". - Calling
trial()again on the same machine while it runs resumes that same trial; it never opens a second one. Two first calls racing each other still create only one: the later one getstrial_used, and callingtrial()again resumes the trial just created. - The answer carries
trial.endsAt(when it ends) andtrial.fallback(whether the machine is now on the fallback plan). - If the calling IP has a risk score of 70 or more in Protect 24/7, no new trial can start from it (
trial_denied). - When the user activates a real key with
activate()on a machine that is trialling, the trial is marked as converted and kept for the record. From then ontrial()on that machine returnstrial_used.
When the trial ends
After the end date, the next trial() call on that machine follows the After the trial setting:
- Stop: the server returns
trial_expired. Your software should invite the user to buy a key. - A fallback plan: the trial license moves to that plan and carries on with its machines, its term (counted from the switch, empty means lifetime) and its entitlements. The answer has
trial.fallback = true. A common setup is a lifetime "Free" plan with fewer entitlements.
Open sessions get license_expired once the trial license runs out (after the plan's grace days, if any). When a trialling machine sees that code, call trial() again to move it onto the fallback plan.
Maintenance and minimum version
Once past the mode check, every activate, trial, register and login call goes through one shared gate, in this order: maintenance mode, minimum version, blocked builds, then unknown builds. The first two settings live in Licensing & releases › Releases.
Maintenance mode
- Go to Licensing & releases › Releases.
- Turn on Maintenance mode.
- Type a Message during maintenance (up to 300 characters). It is saved when you leave the field.
During maintenance, init() returns app.maintenance = { message } (an empty string if you typed no message) so your software can show the message instead of a sign-in form. The four sign-in calls are refused with HTTP 503 and the code maintenance, carrying your message (or a default English sentence when you leave it empty). Open sessions are not cut: heartbeats, variables and redeem keep working. Maintenance only stops new sign-ins.
Minimum version
- In the Minimum version card on the same page, type a semver version (for example
1.4.0) next to thestablechannel and click Set. Clear the field and click Set to remove the limit. - In the SDK, pass
appVersion(ideally withbuildHash) when you createClavisClient.
At sign-in, the server compares the appVersion the SDK sends with the minimum version of the stable channel. Older builds are refused with HTTP 426 and the code update_required. If the SDK sends no appVersion, this step is skipped, so always set it. init() warns ahead of time with app.update.required = true and minVersion, so you can show an update screen before the user types anything.
The other two checks of the gate:
- A version or build file belonging to a release whose status is Blocked is refused with
build_blocked. - When Build Integrity Verification is turned on in Advanced Protection and set to Block, a
buildHashthat matches no file in the app's releases is refused withunknown_build. This check only runs when the SDK sends abuildHashand the app has at least one Published release.
Sample code
These examples use the JavaScript SDK. Create the client once, read the configuration with init(), then show the right screen:
import { ClavisClient, ClavisError, hashComponent } from "@clavisauh/sdk";
// Hash each component before it leaves the machine; the fingerprint hashes the whole set
const parts = { cpu: hashComponent(cpuId), board: hashComponent(boardSerial), disk: hashComponent(diskSerial) };
const clavis = new ClavisClient({
baseUrl: "https://api.example.com",
appId: "app_…",
appPublicKey: "<your app public key>",
device: { fingerprint: hashComponent(Object.values(parts).join("|")), components: parts, platform: "windows" },
appVersion: "1.4.0",
});
const { app } = await clavis.init();
if (app.maintenance) showMaintenance(app.maintenance.message);
else if (app.update?.required) showUpdate(app.update.minVersion);
else showSignIn(app.loginMode, app.trial);
License key mode
if (key) {
await clavis.activate(key);
} else if (app.trial) {
const grant = await clavis.trial();
showTrialBanner(grant.trial?.endsAt, grant.trial?.fallback);
}
Account mode
// No key: the account runs on the default plan
await clavis.register(null, "minh.tran", password, "minh@example.com");
// With a key: the account is tied to that key's license
await clavis.register(key, "lan.pham", password);
// Later sign-ins
await clavis.login("minh.tran", password);
Account + license key mode
await clavis.login("minh.tran", password);
await clavis.redeem(key); // upgrades in place, keeps the username, password and machine
if (clavis.has("pro")) unlockPro();
After redeem, the old session is closed and the SDK picks up the new one from the answer. The free license created at sign-up is revoked, since it has nothing left to do. A license can be tied to one account only.
Handling errors
try {
await clavis.login(username, password);
} catch (e) {
if (!(e instanceof ClavisError)) throw e;
if (e.code === "maintenance") showMaintenance(e.message);
else if (e.code === "update_required") showUpdate();
else if (e.code === "mode_not_allowed") await clavis.init(); // the mode just changed, read it again
else showError(e.message);
}
Common error codes
| Code | HTTP | Meaning |
|---|---|---|
mode_not_allowed | 403 | The call is not part of the current sign-in mode |
maintenance | 503 | The app is in maintenance |
update_required | 426 | The version is below the minimum |
build_blocked / unknown_build | 403 | A blocked build, or one you never released |
trial_unavailable | 404 | No plan has trial days |
trial_used | 409 | The machine used its trial and moved to a key, or it is the later of two racing first calls |
trial_denied | 403 | The IP has a high risk score, no new trial |
trial_expired | 403 | The trial ended and there is no fallback plan |
username_taken | 409 | The username is already in use |
license_already_registered | 409 | The license is already tied to another account |
user_limit | 403 | The end-user limit of your service plan is reached |
invalid_credentials | 403 | Wrong username, password or key |
user_banned / no_license | 403 | The account is banned, or has no license |
account_required / already_redeemed | 403 / 409 | redeem needs an account session; the key is already yours |
too_many_attempts | 429 | 20 wrong keys or passwords from one IP to the same app within 15 minutes |
End-user limits per service plan are listed in Basic, Premium and limits.