---
title: "Licensing"
description: "Sell your app with signed licence files checked offline, and tell app licences apart from TAPS licences, API keys and entitlements."
section: "concepts"
status: "Building for 0.4"
spec: "licence"
canonical: "https://mainsdk.dev/docs/concepts/licensing"
updated: "2026-10-28"
---

# Licensing

> Sell your app with signed licence files checked offline, and tell app licences apart from TAPS licences, API keys and entitlements.

Status: Building for 0.4 · Canonical: https://mainsdk.dev/docs/concepts/licensing · Updated: 2026-10-28

> **Note:** The in-app verifier (spec `licence`) is built and runs from the repository. Issuing from MainSDK Cloud is planned for 0.6 (spec `licence-cloud`); until then you issue licences with your own CA or the testing kit.

An app licence is your signed permission for one install or one tenant to use your app's features. You issue it from your MainSDK Cloud account; your app checks it offline against a public key you pin in `main.yaml`. Nothing calls home while a request runs, and the app keeps serving when nothing is reachable (ADR-0070).

```ts
import { licence } from "mainsdk";

licence.require("sso");                       // 403 unless the licence in scope includes sso
const { status, graceEndsAt } = licence.state();
```

```yaml title="main.yaml"
licence:
  driver: http
  product: acme-desktop
  roots: ["ed25519:CvBKNOIhJSGvXg9chN9qV8-z6byK3-is6-12mXaidWQ"]   # the well-known test root: pin your own in production
  file: ./main.licence
  features: [sso, audit-export]
```

## Four things that sound alike

| | App licence | TAPS licence | API key | Entitlement |
| --- | --- | --- | --- | --- |
| What it answers | May this install or tenant use this feature of the app? | May this person open this protected file, now? | Which customer is calling the app's API? | What does this tenant's plan include? |
| Who grants it | You, the vendor, from MainSDK Cloud | A TAPS Node, under the file's policy | Your app, to its own customers | Your app, from its plans and the tenant's subscription |
| Who holds it | The customer's install or tenant, as a file or token | The person's open session | The customer's code | Nobody: it is a check in your code |
| How long it lasts | Months, renewed by refresh or a new file | Seconds, renewed every `protect.renew` | Until rotated or revoked | As long as the plan |
| Checked where | In the app, offline, against a pinned root | By the TAPS Node, online | In the app, against a SHA-256 digest | In the app, from memory |
| Interface | `licence` | `protect` | `apikeys` | `entitlements` |
| Call | `licence.require("sso")` | `protect.open(asset, { purpose })` | `apikeys.verify(req)` | `entitlements.require("export")` |
| When it can't be checked | Valid stays valid; only expiry plus grace stops it | Nothing opens | The request is refused | The last known plan is served |

They work together. A feature you sell under licence goes under `licence.features`; a tenant then has it only when its plan includes it and the licence does too, and a licence's `seats` or limits can lower a plan's limit but never raise it. A tenant licence can also name the tenant's plan. An app licence may carry a reference to a TAPS licence or asset for your own use, but `licence` never evaluates it and `protect` never reads app licences.

## How a licence is checked

Each licence file holds two signed parts: the licence, signed by your issuing CA, and the CA's certificate, signed by your root. Your app pins the root's public key, so it can check the whole chain with no network. Everything is Ed25519 on the platform's WebCrypto, in a DSSE envelope over canonical JSON, so one changed byte fails.

| State | Licensed features | When |
| --- | --- | --- |
| `valid` | On | Signed by a pinned chain, inside its dates, not revoked |
| `grace` | On, with a daily warning | Expired, or revoked or suspended by a signed list, within its grace |
| `expired`, `revoked`, `suspended` | Off | After grace |
| `tampered` | Off | Doesn't verify: signature, chain, format, wrong project or product, wrong environment or machine, older than one already seen, or the test root in production |
| `missing` | Off | No licence, or not valid yet |

Only licensed features turn off. The app always starts and serves, and no other protection depends on a licence.

## Revocation and refresh

You revoke or suspend a licence in the Cloud, which publishes a signed revocation list. With the `http` driver your app fetches new lists and renewed licences in the background, never while a request waits. With the `offline` driver you copy a trust file in. An unreachable list never blocks a valid licence, so offline revocation is bounded by the licence's own expiry: give online installs short licences that refresh.

The app remembers the newest licence, the newest list and the latest time it has seen, so an older licence, an older list or a clock set backwards doesn't bring an expired licence back.

## What it promises, honestly

A licence can't be forged or edited without your CA's private key, which never leaves the Cloud's key service, and an app that has seen something newer can't be rolled back through anything MainSDK reads. That keeps honest customers honest and makes your terms auditable.

MainSDK is open source and runs on your customer's machine. Someone who controls that machine can patch the check out or keep the machine offline. The [seal](https://mainsdk.dev/docs/concepts/security-model) reports changed files, but it can be rewritten too. Serve features worth stealing from a server you run.

## Try it locally

`mainsdk dev` uses the `local` driver: with no licence file, it serves a dev licence with every feature under `licence.features`, signed by a test root that `mainsdk check` refuses in production. In tests, `createTestLicenceCA()` from `@mainsdk/testing` issues, revokes and tampers with licences. To inspect a file, run `mainsdk licence show main.licence`, and `mainsdk licence verify` in CI.

## Next

- [App licensing](https://mainsdk.dev/docs/interfaces/licence): the calls, `main.yaml` keys and testing kit
- [License your app](https://mainsdk.dev/docs/guides/license-your-app): the steps, start to finish
- [Free in the app, paid in the Cloud](https://mainsdk.dev/docs/concepts/free-and-cloud)
- [Security model](https://mainsdk.dev/docs/concepts/security-model)
- [Check codes](https://mainsdk.dev/docs/reference/check-codes)
