# 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.

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

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.

---

Agentic Boilerplate: A Next.js repo your agent already knows. Free during launch, then $99 once.

- Site map for agents: https://agenticboilerplate.com/llms.txt
- Public API: https://agenticboilerplate.com/openapi.json
- Contact: agenticstudio@gmail.com
