MainSDK
Search⌘K
Docs

Licensing

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

Building for 0.4

Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.

Stability The promise an interface ships with.

  • Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
  • Beta Usable in production; may change before it becomes stable.
  • Experimental May change or go away; needs an explicit opt-in in main.yaml.
Stability levels
Updated 2026-10-28

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();
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 licenceTAPS licenceAPI keyEntitlement
What it answersMay 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 itYou, the vendor, from MainSDK CloudA TAPS Node, under the file's policyYour app, to its own customersYour app, from its plans and the tenant's subscription
Who holds itThe customer's install or tenant, as a file or tokenThe person's open sessionThe customer's codeNobody: it is a check in your code
How long it lastsMonths, renewed by refresh or a new fileSeconds, renewed every protect.renewUntil rotated or revokedAs long as the plan
Checked whereIn the app, offline, against a pinned rootBy the TAPS Node, onlineIn the app, against a SHA-256 digestIn the app, from memory
Interfacelicenceprotectapikeysentitlements
Calllicence.require("sso")protect.open(asset, { purpose })apikeys.verify(req)entitlements.require("export")
When it can't be checkedValid stays valid; only expiry plus grace stops itNothing opensThe request is refusedThe 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.

StateLicensed featuresWhen
validOnSigned by a pinned chain, inside its dates, not revoked
graceOn, with a daily warningExpired, or revoked or suspended by a signed list, within its grace
expired, revoked, suspendedOffAfter grace
tamperedOffDoesn't verify: signature, chain, format, wrong project or product, wrong environment or machine, older than one already seen, or the test root in production
missingOffNo 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 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

Was this page helpful?

↑↓ moveEnter openEsc close

Not decided yet