Skip to content

Licence keys for a desktop or command-line app

A paid desktop or command-line app needs three things from its licensing: to know that a key is genuine, to count the machines it runs on, and to keep working when the network does not. This page puts the pieces together for an app you ship; each step links to the page with the details.

Keep these together, in the app’s own settings or data directory, per user:

Item Where it comes from Why
The licence key The customer enters or pastes it It authenticates every call and carries the licence
The machine id A random UUID the app generates once per install Sealcord counts a seat per machine id
The latest verdict The token of the last activation or status answer Lets the app run offline until the verdict’s end
The public key Built into the app, from the product’s page in the Console Checks the key and the verdict with no network

Never store or send a hardware serial or anything personal as the machine id. No admin API key ships in the app: the licence key is enough for every call the app makes.

  1. The customer enters a key. Check it offline first: its signature, its product and its expiry. A key that fails is refused at once, with no call.
  2. Activate this machine. Call POST /v1/activations with the key, the machine id, a label the customer will recognise (the computer’s name), the system and the app’s version. Check the answer’s token as a verdict and store it.
  3. On later launches, check the stored verdict offline. While it is valid, the app runs with no call.
  4. Refresh it at most once a day, or when it has expired: call POST /v1/activations/status and store the new token.
  5. When the customer signs out or moves to another computer, call POST /v1/activations/deactivate to free the seat.

A network error, a timeout or a 5xx never means the licence is gone. Keep the last good result and try again later.

What the API answers What the customer sees
An active verdict Nothing: the app runs
revoked with reason not_activated Nothing: the app activates once and carries on
revoked with reason revoked, or licence_revoked The licence no longer holds, and how to reach you
revoked with reason expired, or licence_expired The licence has ended, and where to renew
seat_limit_reached Every seat is in use (details.limit), and how to free one
licence_key_invalid or licence_wrong_product The key is not for this app; check it was copied whole
No answer, 429 or 5xx Nothing: the app keeps its last good result and retries later

Unless you turn it off, customers can free their own machines in the Console under My licences, so the seat message can point there, next to the app’s own “Sign out” button (Free a seat).

The same calls work from a terminal tool. A few things differ:

  • Entering the key. Offer a command such as acme-notes activate ACN1-…, and read the key from an environment variable as well, so scripts and build machines can set it without a prompt.
  • The label. Use the host name, so the customer can tell machines apart when freeing a seat.
  • Short-lived machines. A container or a build runner that starts fresh each time makes a new machine id, and so takes a new seat on each run. Give such machines a stable id from their configuration, or issue that customer a licence limited by machines with room for them (seats or machines), and deactivate when a machine is retired.
  • Exit codes. Keep running on a network error, as a desktop app does; fail with a clear message only when the API answers that the licence no longer holds.

Issue one licence per paid order from your checkout’s backend with the Admin API (Issue from your backend), or connect your own Polar or Stripe account and let its webhooks issue, revoke and expire licences for you.

Yes. Issue a trial: an active licence with an end date, which your app treats like any other (Trials).

What happens if Sealcord is unreachable on launch day?

Section titled “What happens if Sealcord is unreachable on launch day?”

The offline check of the key needs no network, and a stored verdict keeps the app running until its end. For a new customer whose first activation gets no answer, the app decides: running on the offline check while it retries is the safe default (Apps that run offline).

No. The app checks the stored verdict offline on each launch, and asks the API at most once a day.