Skip to content

Self-hosting

Licence

Install the offline licence key, what it unlocks, and what happens when it expires.

A self-hosted install runs without a licence key. A key from Supermode unlocks the Team or Enterprise feature set. It is checked offline, never calls home, and never locks anyone out of their data.

What works without a key

Uploading and publishing, share links in every access mode except members-only, commenting, voice notes, reactions, presence and follow mode, folders, the crit CLI and the MCP round-trip with your coding agent. Plan limits (how many prototypes, how large a package) come from each workspace's plan, which the operator sets:

bash
pnpm --filter @critlab/db set-plan <workspace-slug> pro

What a key unlocks

FeatureTeam editionEnterprise edition
Custom share domains✓✓
Integrations (Slack-compatible webhooks; Linear and Jira when enabled)✓✓
Members-only prototype access✓✓
Audit log export✓✓
SAML single sign-on and verified email domains✓
SCIM directory sync✓
Bring your own bucket, region pinning✓

A key can also name its features explicitly, for example Team plus SSO. The key carries a seat count and an expiry date, and may be bound to one deployment by name.

On a self-hosted install, features come from the key, not from the plan: set-plan <slug> enterprise raises limits and grants no features. A refused feature answers feature_not_licensed (HTTP 402) with a message naming the feature.

The key

A key is one line: critlab-lic.<payload>.<signature>. The payload names the licensee, edition, seats, features, issue and expiry dates and, optionally, an instance id. The signature is Ed25519, verified with Node's own crypto against a public key compiled into the build — no network access, which is what makes it work air-gapped.

Install a key

Helm, from a Secret you manage (recommended):

bash
kubectl -n critlab create secret generic critlab-license \
  --from-literal=LICENSE_KEY='critlab-lic.…'
helm upgrade critlab charts/critlab -n critlab --reuse-values \
  --set license.existingSecret=critlab-license

license.existingSecretKey renames the key inside the Secret if LICENSE_KEY does not suit you. license.key puts the token in the chart-managed Secret instead.

From a file: set LICENSE_KEY_FILE=/etc/critlab/license.key (for example a mounted Secret). LICENSE_KEY wins if both are set.

From the app: a platform admin can paste a key in Settings → License. The key is verified first, then stored at LICENSE_KEY_FILE if that path is writable; otherwise the page tells you which variable or Secret to set. A key that has expired past its grace period is refused, because installing it would switch the licensed features off for everyone.

A license decides what every workspace on the instance may do, so Settings → License belongs to the operator, not to a workspace owner (everyone who signs up owns a personal workspace). One step first: make your operator account a platform admin, from a checkout with DATABASE_URL pointing at your database, as with set-plan:

bash
pnpm --filter @critlab/web admin:grant --email you@example.com

Sign in as that account and Settings → License appears. Anyone else gets a 404 for the page and for /api/license. crit license and the critlab_get_license / critlab_set_license tools no longer work, because a platform-admin setting is never reachable with an API key.

Bound keys: if your key was issued for one deployment, set INSTANCE_ID on web to the name it was issued for (through web.extraEnv with Helm). A bound key is refused anywhere else.

web re-reads the key at most once a minute, so installing or renewing one needs no restart. The licence lives in the deployment, never in the database: restoring an old database backup does not restore an old licence.

Check it

Settings → License (platform admin only) and GET /api/license called with that admin's browser session report the licensee, edition, seats used against seats allowed, expiry, the exact feature list and, if something is wrong, the verifier's reason — a signature that does not match this build, a key bound to another instance, an expired key.

Expiry and grace

StateWhenEffect
ValidBefore the expiry dateEverything the key grants works.
GraceUp to 14 days after expiryEverything still works; owners see a renewal banner on the dashboard.
ExpiredAfter the grace periodThe licensed features go read-only. Prototypes, versions, comments, transcripts and sign-in are never affected.

There is no revocation list — offline verification cannot have one — so keys are issued with sensible expiry dates and renewed.