The CLV1 protocol is now publicRead the spec
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.

ModeName in the dashboardAccepted callsUse it when
keyLicense keyactivate, trialYou sell keys and users need no account
accountAccountregister, loginYour software has its own accounts, a key is optional at sign-up
account_keyAccount + license keyregister, login, redeemSign-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 through trial().
  • 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

  1. Open the app and go to Licensing & releases › Sign-in & license plans.
  2. 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.
  3. 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

  1. Go to Licensing & releases › Sign-in & license plans and select License key mode. Trials only exist in this mode.
  2. Under License plans, click the plan to use for trials, or click Add plan.
  3. In the Trial section, enter Trial days (1 to 90).
  4. In After the trial, choose another plan of the app as the fallback, or Stop.
  5. 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() returns app.trial = true when the app is in License key mode and at least one plan has trial days. Show a "Try it" button only when this is true.
  • 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 a TRIAL- 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 gets trial_used, and calling trial() again resumes the trial just created.
  • The answer carries trial.endsAt (when it ends) and trial.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 on trial() on that machine returns trial_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

  1. Go to Licensing & releases › Releases.
  2. Turn on Maintenance mode.
  3. 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

  1. In the Minimum version card on the same page, type a semver version (for example 1.4.0) next to the stable channel and click Set. Clear the field and click Set to remove the limit.
  2. In the SDK, pass appVersion (ideally with buildHash) when you create ClavisClient.

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 buildHash that matches no file in the app's releases is refused with unknown_build. This check only runs when the SDK sends a buildHash and 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

CodeHTTPMeaning
mode_not_allowed403The call is not part of the current sign-in mode
maintenance503The app is in maintenance
update_required426The version is below the minimum
build_blocked / unknown_build403A blocked build, or one you never released
trial_unavailable404No plan has trial days
trial_used409The machine used its trial and moved to a key, or it is the later of two racing first calls
trial_denied403The IP has a high risk score, no new trial
trial_expired403The trial ended and there is no fallback plan
username_taken409The username is already in use
license_already_registered409The license is already tied to another account
user_limit403The end-user limit of your service plan is reached
invalid_credentials403Wrong username, password or key
user_banned / no_license403The account is banned, or has no license
account_required / already_redeemed403 / 409redeem needs an account session; the key is already yours
too_many_attempts42920 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.

How can we help?

Frequently asked

What is ClavisAUH?

A licensing and authentication service for desktop software: license keys, device binding, end-user sign-in and signed responses. All run from one dashboard

Why can't a cracker fake your server?

The SDK only trusts answers signed with your app's private key. That key never leaves our servers, so a fake server can't sign and gets rejected

What ends up in my binary?

Your app id and public key. Nothing secret, because each device makes its own key pair on first run

Does it work offline?

Yes. Every plan gets a signed offline token for 7 days, and your app checks it locally. Premium lets you choose how long. A revoked license can keep working offline for that time, unless Online-Only Mode is on

Which languages are supported?

JavaScript and TypeScript today. C++, C# and Python are next, on the same open protocol

Is Basic really free?

Yes. No time limit, no card. Move to Premium when you need more

Get in touch