Reclaim license seats from machines that are gone
A machine takes a seat when it activates and keeps it until something deactivates it. Machines often go away without doing that: a customer reinstalls the operating system, replaces a laptop, or a computer dies. Each one keeps its seat forever.
So a customer who bought three seats and has owned four laptops is locked out of their own license, and the first you hear of it is an email. This guide frees that seat now with Casazium License Server — hosted, or self-hosted — and then stops it happening again.
It assumes you've already capped machines with max_activations — the
number of seats a license has. If you haven't, start with
Limit a license key to a set number of computers.
Free a stuck seat now
In the console
On a hosted account: Licenses → the license → Activations → Deactivate, on the row of the machine that's gone, then confirm.
Each row is a machine holding a seat: its Instance (the ID your app sent when it activated) and Activated at, plus Last check-in on a license with a lease. The seat is free at once, and that machine would have to activate again to use the license. Using the Console describes the table. The self-hosted bundle's console has the same page.
Through the API
Where these calls go. Self-hosted, call your own server with its
admin API key; https://your-license-server.example.com stands in for
your address (the self-hosted bundle answers on http://localhost:3001
on the machine running it). On a
hosted account, send them to https://license-cloud-api.casazium.com
— not license-cloud.casazium.com, which is the console — and use your
API key from Settings → API access. A hosted account never accepts
ADMIN_API_KEY.
First, see which machines hold seats:
curl https://your-license-server.example.com/v1/list-activations/7yChyZcfMG23Dx1sjBoLziPFrH4n-6f28 \
-H "Authorization: Bearer $ADMIN_API_KEY"
{
"key": "7yChyZcfMG23Dx1sjBoLziPFrH4n-6f28",
"activations": [
{
"instance_id": "unique-instance-id",
"activated_at": "2025-01-01T00:00:00.000Z",
"lease_expires_at": null,
"last_seen_at": null,
"lapsed": false
}
]
}
Each instance_id is the string your app sent when that machine
activated — you chose how it's derived, and
Limit a license key to a set number of computers
covers how.
Newest first. lease_expires_at, last_seen_at and lapsed are
populated only once the license has a lease — without one they're
null/false, so on a license you've never leased you're identifying
the dead machine by instance_id and activated_at alone.
Then free the one that's gone, without needing that machine's token:
curl -X POST https://your-license-server.example.com/v1/admin/deactivate-by-instance-id \
-H "Authorization: Bearer $ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"key": "7yChyZcfMG23Dx1sjBoLziPFrH4n-6f28",
"instance_id": "the-dead-laptop"
}'
You don't have to be the one doing this. The customer's own app can
free its seat with POST /v1/deactivate-license, using the key and the
activation token it stored — no admin key, so you can ship this in a
distributed app.
If the app lost its activation token
This doesn't free a seat; it's the neighboring problem. An app that
lost its token can't deactivate itself or report usage. In the console,
Reissue token on that machine's row under Activations issues a
new one; through the API, POST /v1/admin/reissue-token does the same
for that key and instance_id. Either way it invalidates the old
one, and the seat stays where it is. It doesn't recover the original
token; nothing does.
Stop doing this by hand: set a lease
A lease is how long a machine may go without checking in before its seat is considered available. A machine past its lease gives up its seat only when another machine needs one.
This needs License Server 1.14.0 or later, and console 1.10.0 or later to set it from the UI.
Don't set a lease if your app only verifies the key
Leases work by machines checking in. Two calls identify a machine and so
count as check-ins — validate-license and track-usage. Others don't:
// A check-in: identifies this machine, so its lease renews.
const { valid } = await client.validate(licenseKey, instanceId);
// NOT a check-in: identifies the license, not the machine.
await client.verifyKey(licenseKey, MY_PRODUCT_UUID);
verify-license (the SDK's verifyKey), verifying an offline license
file, and check-update never look at activations.
So if your app only verifies the key, none of its machines ever check in. Each lapses one lease period after activating, and its seat goes to the next machine that asks — while the machine that lost the seat carries on running, because nothing it calls checks seats. Your seat cap stops holding after one lease.
Leases are for apps that call validate() or track-usage. If yours
doesn't, leave leases off. (validate() needs SDK 0.4.0 or later;
where it goes in a real app is in the
desktop guide.)
In the console it's Free inactive machines — on the issue form, in Edit terms, and on storefront mappings. The choices are Never (the default), After 30 days, After 90 days, or a custom 1 to 365 days.
Over the API it's lease_seconds: a whole number from 86400 (1 day)
to 31536000 (365 days), or null for none.
curl -X POST https://your-license-server.example.com/v1/admin/update-license-terms \
-H "Authorization: Bearer $ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"key": "7yChyZcfMG23Dx1sjBoLziPFrH4n-6f28",
"lease_seconds": 7776000
}'
7776000 is 90 days.
A lease does nothing on a license with unlimited seats
If max_activations is null — the console's Unlimited seats — no
seat is ever counted and nothing is ever reclaimed. Machines still get
lease dates, so the console and the portal will mark them inactive and
say the seat goes to the next machine that needs one, when no seat will
ever move. Leases only mean something alongside a seat cap.
Storefront licenses take the mapping's lease
A storefront mapping overwrites lease_seconds the same way it
overwrites the seat count, and a mapping with no lease set turns off one
you set by hand. Set it on the mapping —
why, and how to fix it.
Check-ins only renew a lease in its second half
A check-in while more than half the lease remains doesn't extend it; once less than half is left, the next check-in starts the lease over. Treat half the lease as a boundary rather than a safe interval — choose a length with that in mind.
Activating again with the same key and instance_id also counts, but
use validate() — activation is rate-limited per license key and
validation isn't.
This all depends on instance_id being stable for the machine: a
value computed fresh at runtime, common in containers, makes every
launch a new machine and leases then churn seats instead of returning
them. Limit a license key to a set number of computers
covers how to derive one.
What happens when a lease runs out
Nothing happens to the machine until another machine needs its seat. Until then it keeps working, keeps validating, and its next check-in renews the lease as if nothing had happened. It still counts toward the cap. The console marks it inactive and shows when it was last seen, as does the customer's portal where that's enabled (it's off by default on a self-hosted install).
When a new machine activates and no seat is free, License Server removes
inactive machines, longest-lapsed first. It removes as many as it needs:
one if the license was exactly full, several if it holds more machines
than its current cap — which happens when you lowered max_activations,
or a storefront cut the seats, neither of which deactivates anything on
its own. If there aren't enough inactive machines, the activation is
refused as it would be without a lease, and no machine is removed.
A removed machine finds out at its next check-in: validate-license
answers {"valid": false} and track-usage answers 403. Handle that
the way you already handle a customer freeing a machine — ask them to
activate this one again. (activate-license returns lease_expires_at
when a lease is set, if you want to show a customer when a seat lapses.)
Choose more than twice the longest gap between uses
- Twice the gap is the boundary, not the floor, for the strict comparison above. A weekly app wants 30 days, not 14. An occasional one, 90 or more.
- Allow for offline use. A machine with no network doesn't check in,
so make the lease at least twice the longest offline period you allow.
Offline license files
aren't seat-limited at all: a file keeps working until the license
expires whether or not its machine still holds a seat, and a machine
whose seat is reclaimed mid-run carries on until its next online
validate(). - When in doubt, longer. Too short costs a customer a seat only when they're also activating elsewhere; longer just returns abandoned seats more slowly.
Turning it on for licenses you've already sold
Each existing machine starts a full lease from that moment, whenever it last checked in — so nothing lapses immediately, even machines gone for a year. Machines in use renew as they check in; the rest go inactive one lease later and their seats come back.
Until a machine first renews, the console shows its last check-in as Not yet since the setting was turned on, even for a machine in daily use. That's expected, not a fault.
Shortening a lease cuts any longer remaining lease down to the new length, counted from the change — so a machine long gone becomes inactive one new lease period later, not instantly. Lengthening applies from each machine's next renewal. Never makes every seat permanent again.
Leases are not concurrent or floating licensing
A lease is at least a day long. It returns seats held by machines that are gone, not by apps that are closed. Ten people cannot share five seats by taking turns.
To move a seat immediately, use Deactivate in the console,
deactivate-by-instance-id above, or the customer's own
deactivate-license.
Where to go next
- Limit a license key to a set number of computers — the seat cap itself, and what it does and doesn't stop.
- Seats that free themselves — the reference for everything on this page.
- Licensing an Electron or Tauri desktop app — where the launch check that doubles as a check-in lives.
More in Guides, or the full documentation.