A web app checks the session. A desktop app, a CLI or an editor plugin has no session. It has a key the customer pastes in. Lemon Squeezy issues and validates those keys for you.
Turn them on per variant
On the variant, enable Generate license keys. Set:
- Activation limit: how many machines one key may activate. 1 to 5 is common for personal licences.
- License length: unlimited, or a fixed number of days, months or years.
For a subscription variant, the key follows the subscription. It expires when the subscription expires and comes back when it is renewed.
After purchase the customer gets the key in the receipt email and in the
customer portal. A license_key_created webhook also fires with the key
object if you want your own copy.
The License API is public on purpose
Three endpoints, no API key, safe to call from the customer's machine:
| Call | When |
|---|---|
POST /v1/licenses/activate (license_key, instance_name) | First run on a machine. Returns an instance.id |
POST /v1/licenses/validate (license_key, instance_id) | On startup, or once a day |
POST /v1/licenses/deactivate (license_key, instance_id) | User moves to a new machine |
The JS SDK wraps them as activateLicense, validateLicense and
deactivateLicense. Never ship your Lemon Squeezy API key in a desktop
build to call anything else. It can refund orders and read every customer.
Anything that needs it goes through your server.
Check that the key is yours
A valid key from somebody else's Lemon Squeezy store also validates. The
response includes meta.store_id and meta.product_id. Compare both with
constants baked into your build. Skip this and any key bought for $1 in a
different store unlocks your app.
Activation flow that survives real users
- On first run, ask for the key. Call activate with a human instance name
("Maya's MacBook Pro"). Store the
license_keyandinstance.idlocally. - On later runs, call validate with both. Grant access if
validis true andlicense_key.statusisactive. - Cache the last good result with a timestamp. If the network is down, keep working for a grace period (7 days is common). Do not lock out a paying customer on a plane.
- On "activation limit reached", show the list of instances from the customer portal and let them deactivate an old machine. That one screen removes most licence support tickets.
Statuses
license_key.status is one of inactive (never activated), active,
expired (the length ran out, or the subscription expired) or disabled
(you turned it off, or the order was refunded). Only active unlocks.
Keys and your web app
If you sell both a web app and a desktop app on one subscription, gate the web
app on the subscription status (it is richer: trials, past_due grace,
cancellation dates), and gate the desktop app on the key. Both follow the same
subscription, so they agree without you syncing anything.