Licensing

Farna Control runs on the Community tier without a license key. A Team license unlocks the administrative capability group: single sign-on, directory provisioning, team-level access control, the audit viewer and its exports, cost controls, and the operator configuration surfaces listed below.

A license is an Ed25519-signed key. Your instance verifies the signature and the expiry locally, so licensing never needs a network connection to Farna Labs.

Tiers

Tiers
Tier License key What it covers
Community None Pipelines, runs, connectors, model backends, schemas, agents, the library, human-in-the-loop (HITL) review, evals, the Model Context Protocol (MCP) server, and the core cost safeguards.
Team A signed key The 17 capability flags in the next table.

Executing a pipeline, recording a run, and reading run data through the API are not license-gated. Team gates administrative and configuration surfaces, not the work the platform performs.

What Team adds

The feature registry in backend/src/modulo/core/feature_flags.py lists 17 flags in the Team tier. A Team license activates the tier by rank, so one valid key turns on all of them.

What Team adds
Capability Flag
Single sign-on (SSO) over OpenID Connect (OIDC) or Security Assertion Markup Language (SAML) 2.0 sso
System for Cross-domain Identity Management (SCIM) 2.0 user and group provisioning scim
Team-level role-based access control (RBAC) team_rbac
Audit viewer, chain verification, paginated and full-chain audit exports, signed evidence packs, and the compliance evidence index audit_viewer
Organization daily spend limits and budgets admin_spend_limits
Budget overview, team budgets, alert thresholds, and billing settings admin_cost_controls
Monthly cost breakdown and anomaly detection admin_cost_breakdown
Email (Simple Mail Transfer Protocol, SMTP) configuration email_config
External error tracking integrations error_tracking
Dispatch errors to external services through webhooks error_forwarders
Plugin, connector, and node category management plugin_management
Run retention policies and manual purge admin_run_retention
Schema version history and diff schema_version_history
API rate limit configuration rate_limits
Runtime configuration overrides runtime_config
Sandbox environment profiles environment_profiles
Hard-deleting pipelines from the UI pipeline_delete

Team and Community both capture run-level data and append significant governance events to the organization-scoped audit chain. The tier difference is the administrative evidence surface: Community can read run data through the API, while Team adds the audit viewer, chain verification, paginated and full-chain exports, signed evidence packs for offline verification, and the compliance evidence index. A signed evidence pack is tamper-evident and independently checkable, not tamper-proof and not a compliance certification. See Which tiers are there, and what does each include?.

Installing a license

You install a license at one of two scopes, from Settings > License at /settings/license. Both scopes verify the key before storing it, so an invalid key is rejected rather than saved. Installation also needs a trust anchor that can verify the key: see The trust anchor.

Organization scope

Use this scope when one organization on a shared deployment has bought the license.

  1. Sign in as an organization admin. The scope needs the org.license.manage permission, which the admin role carries.
  2. Open Settings > License and find the Organization License card.
  3. Paste the key and select Apply Key.

The key is written into the organization’s settings in the database, so it survives a restart or redeploy. It applies to that organization only, and it takes precedence over a deployment-wide key for that organization.

The equivalent API call is PUT /api/v1/admin/orgs/{org_id}/license.

System scope

Use this scope when the whole deployment should be licensed, for every organization that does not have a key of its own.

  1. Sign in as a system administrator. This scope needs system.config.manage, which only a system administrator holds.
  2. Open Settings > License and find the System License card.
  3. Paste the key and select Install System License.

The key is verified, then written to the instance’s system_config table and loaded into memory. It takes effect immediately, and it survives a restart or redeploy: each process restores the stored key at startup, so a rolling deploy does not drop it.

Other processes see the install without a restart. The API re-reads the stored key when it resolves a plan, and the queue worker re-reads it at the start of every job, so a job runs on the current key. Each process re-reads at most once every 15 seconds.

The stored key applies to every organization that has no key of its own, and it overrides MODULO_LICENSE_KEY.

The equivalent API call is POST /api/v1/admin/license.

Environment variable

Set both variables and restart the processes. This licenses a whole deployment without installing a key in the UI, and it is the deployment floor: a key installed at system scope overrides it.

MODULO_LICENSE_KEY=<the key>
MODULO_LICENSE_PUBLIC_KEY=<64 hex characters>

The key is verified at every read. An unverifiable, expired, or not-yet-started key in this variable is not applied: the resolver falls through to the next source, or to Community when there is none.

Precedence

Farna Control resolves the first valid key from this order:

  1. The organization’s own key.
  2. A key installed at system scope from the License page, stored in the instance’s database.
  3. MODULO_LICENSE_KEY in the environment.

A key that is invalid, past its grace window, or not yet at its start date falls through to the next step.

The trust anchor

License verification uses one Ed25519 public key: the trust anchor. Farna Control ships with the public half of a development keypair. That development key is trusted only when the instance runs in debug mode, and Farna Labs signs production licenses with a different keypair, so a production key cannot verify against it.

Set MODULO_LICENSE_PUBLIC_KEY to the public key Farna Labs issues with your license. It is 64 hexadecimal characters, and it is required in production. Set it on the API process and on the worker process, and restart both.

MODULO_LICENSE_PUBLIC_KEY=<64 hex characters>

Symptom: a valid key is rejected with an anchor mismatch

If the anchor is not set, installing a correctly signed production key fails with HTTP 422. The response carries reason: anchor_mismatch and a hint that names MODULO_LICENSE_PUBLIC_KEY.

Set the variable, restart, and install the same key again. The key is not the problem.

A related failure has a different reason: if MODULO_LICENSE_PUBLIC_KEY is set but is not the public half of the keypair that signed the license, the reason is signature_mismatch.

Symptom: the process refuses to start

When MODULO_LICENSE_KEY is set in the environment and no usable anchor is available outside debug, the API process and the worker process fail their startup check instead of booting on the Community tier with a license that can never verify. The startup error names MODULO_LICENSE_PUBLIC_KEY.

Either set the anchor or unset MODULO_LICENSE_KEY. An instance with no license at all starts normally on Community.

A key actually signed with the shipped development keypair is refused as a distinct case, dev_anchor_not_trusted. Do not use development keys on a real deployment.

License states

The API derives one state from the license expiry. The frontend renders that state and does not compare dates itself.

License states
State Meaning What an administrator sees
none No valid license resolved. The License page shows Community. No notice elsewhere.
active More than 14 days before expiry, or a license with no expiry date. No notice. The License page shows Team.
renewal_due 14 days or fewer before expiry, including the expiry instant. A countdown banner above the page content on every authenticated route.
grace Past expiry, within 14 days after it. Team capability still works. A dialog that returns on each visit until the license is renewed.
lapsed More than 14 days past expiry. Team capability is off. A banner stating that Team capability is off, with an action that opens the license key input.

The renewal notice is shown to principals holding org.license.manage, so organization administrators see it and other members do not.

The countdown banner

When 14 days or fewer remain before expiry, a banner appears above the page content on every authenticated route. It names the capability at risk (single sign-on, team roles, and the audit log) and the number of days left.

The banner differs by how the license renews, and the signed key is the only source of that fact:

  • An auto-renewing license reads “Your Team license renews on ” and offers no action. There is nothing to do and nothing to buy.
  • A manually renewed license reads “Your Team license expires on ” and offers the actions to renew (which opens the pricing page) and to install a key.

A license minted before renewal mode was part of the signed key is treated as manual, because auto would promise a replacement that nobody bought.

Grace

Capability expiry is the license’s expires_at plus 14 days. The grace window is the constant LICENCE_GRACE_DAYS in backend/src/modulo/core/license.py. It is not configurable: there is no environment variable, setting, or database column for it.

During grace the Team tier stays on in full. A dialog appears on each visit and states the expiry date and the date the grace ends. It offers two actions with equal weight:

  • I have a new key: opens the license key input.
  • Buy a new license: opens the pricing page.

Both actions exist because the common failure is not slow delivery. A failed charge, an expired card, a declined payment, or a billing contact who has left means no replacement key was ever sent, and the administrator may not be the person holding the card.

Grace covers key delivery, not free use. A self-hosted license is an offline key: an auto-renewal is signed when the charge happens and delivered by email, so a correctly charged renewal arrives at or after the old key’s expiry. Without a grace window, every on-time renewal would briefly disable the instance.

Lapse

After grace, the license grants nothing. The organization resolves to the Community tier, and Team-gated endpoints return HTTP 402 naming the feature and the scope it needs.

No one is locked out:

  • A lapse does not invalidate a session, and it does not gate sign-in. Local password accounts always work.
  • When no valid license resolves, SSO sign-in is allowed through so an SSO-only deployment can still be reached. Entitlement is enforced after sign-in.
  • The License page is gated on org.license.manage, not on a license. An administrator can always reach it and install a new key.

Team capability is off for the whole organization, so the loss applies to every member, not only administrators. The rest of Farna Control keeps working: pipelines run, runs are recorded, and their data is readable through the API.

Renewing early without losing days

Choose when the new term starts when you buy it. The start date sets the window: the term runs from the chosen date, so the new expiry is the chosen start plus the term.

On the pricing page, the Team checkout asks whether you are renewing an existing license. If you are, it offers a native date picker limited to today through 90 days ahead, and the chosen date is carried into the Stripe checkout as starts_at metadata.

The date is future-only:

  • A date in the past is rejected. A date-only value is compared by calendar date, so choosing today is accepted for the whole day.
  • A date more than 90 days ahead is rejected. Exactly 90 days ahead is allowed.
  • An unparseable date is rejected.

These are rejections, not clamps: the purchase webhook refuses the date with starts_at_in_past, starts_at_too_far_ahead, or starts_at_malformed, and no license is issued.

To keep the days you have already paid for, choose the date the current license ends. The new term then begins on the old expiry, and no day is lost. Choosing a later date leaves a gap between the old expiry and the new start. The delivery grace covers the first 14 days of that gap; after that, the instance runs on Community until the successor starts.

A key whose start date has not arrived is valid but not active. It is held as a successor:

  • Installed at system scope before its start date, the current license keeps running. The successor becomes active on its start date without operator action and without a restart. The install response reports parked: true.
  • GET /api/v1/admin/license reports the successor separately from the active license, with its starts_at, its expires_at, and gap_days. gap_days is the number of whole days between the active license’s expiry and the successor’s start, rounded up, and is null when nothing is active or the windows meet or overlap.

Organization scope behaves differently: applying a key there replaces the organization’s current key, and a future-dated key does not grant capability before its start. Install an early-renewal key at organization scope on its start date. To install it ahead of time, install it at system scope, where the current license keeps running until the successor activates. Setting MODULO_LICENSE_KEY to a future-dated key does not keep the current license running: the variable holds one key, so replacing it removes the license in force, and with no other key the deployment runs on Community until the new start date.

Removing a license

Organization scope

An organization admin can remove the organization’s own key. On Settings > License, under Organization License, select Remove License. The organization then falls back to a system-scope key or the environment variable if one is present, and otherwise resolves to Community.

The equivalent API call is DELETE /api/v1/admin/orgs/{org_id}/license.

System scope

A system administrator can remove the key installed at system scope. Under System License, select Remove License. This deletes the stored key, clears the in-memory copy, and clears every cached license status. The change is visible without a restart, it survives the next restart, and other processes see it on their next refresh.

If MODULO_LICENSE_KEY is set in the environment, the deployment stays on Team after this removal, because that variable is a separate source. Unset it and restart to return the deployment to Community.

The equivalent API call is DELETE /api/v1/admin/license. Both removals are audited.

No phone-home

A running instance never contacts Farna Labs to check, refresh, or renew a license. Verification is an in-process Ed25519 signature check against the public key in MODULO_LICENSE_PUBLIC_KEY. A renewal is a key you install. Nothing at runtime depends on Farna Labs being reachable, and an instance with no network access outside its own infrastructure stays correctly licensed.

Troubleshooting

Troubleshooting
Symptom Cause Fix
Install fails with 422 and reason: anchor_mismatch MODULO_LICENSE_PUBLIC_KEY is not set, so only the development anchor is available. Set the anchor to the issued public key and restart.
Install fails with 422 and reason: signature_mismatch The anchor is set but is not the keypair that signed the license. Set the anchor to the correct public key and restart.
Install fails with 422 and reason: dev_anchor_not_trusted The key was signed with the shipped development keypair outside debug. Install the production license issued by Farna Labs.
Install fails with 422 and reason: expired The key is past its expiry plus the 14-day grace. Install a current key.
Install fails with 403 The signed-in principal lacks the scope’s permission. Use an organization admin for organization scope, a system administrator for system scope.
The API will not start, and the error names MODULO_LICENSE_PUBLIC_KEY MODULO_LICENSE_KEY is set with no usable anchor outside debug. Set the anchor, or unset the key.
Team features are off but a key was installed The key lapsed past its grace, the installed key was removed, or a future-dated key has not started. Install a current key at organization or system scope, set MODULO_LICENSE_KEY, or wait for the start date.
A production key is rejected on a development machine The development anchor is trusted only in debug. Set MODULO_LICENSE_PUBLIC_KEY to the production public key.