Skip to content

Lemon Squeezy license keys for desktop apps, CLIs and plugins

Turn on license keys per variant, activate and validate from the client with the public License API, and tie key expiry to the subscription. What to cache and what never to ship.

Lemon Squeezy3 min readships at docs/solutions/lemonsqueezy/license-keys.md

Tags: lemonsqueezy · license-keys · desktop · cli · activation · subscriptions

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:

CallWhen
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

  1. On first run, ask for the key. Call activate with a human instance name ("Maya's MacBook Pro"). Store the license_key and instance.id locally.
  2. On later runs, call validate with both. Grant access if valid is true and license_key.status is active.
  3. 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.
  4. 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.