Docs

Licensing

The licensing plan for Marshal: editions, prices, how licenses work and what’s still open. Agreed and built 2026-10-12; simplified to Free and Pro on 2026-10-14; fixed prices and a new Free/Pro split for small companies on 2026-10-15; online licensing (binding consoles to organisations on the website) built 2026-10-15, and made the only way licenses arrive the same day (license files dropped: air-gapped consoles run Free). Change this file when a decision changes. The rules live in backend/app/services/licensing.py (what is in force) and license_online.py (binding and check-ins); the protocol is LICENSE-PROTOCOL.md in the marshal-website repo.

Why it is shaped this way

  • Built for small companies: in-house IT and small MSPs. Free patches one organisation properly (Windows Server and Linux, windows, reboots, health checks, proxies, mail alerts, compliance), so it is a real product and the way in.
  • Free’s limits are ones small companies hit: a second customer (an MSP), more than 25 machines, more than 3 users. The customer limit alone never bit an in-house IT team, which manages one organisation.
  • Pro sells what a business needs as it grows: no limits, Windows 10/11 endpoints, Microsoft sign-in and an audit log for a team, Teams/Slack/webhook alerts, reports and exports for management and clients, the REST API, support.
  • One fixed price. No steps or sliders: one Pro price, monthly or yearly.
  • Online only. A console bound to its organisation on our website gets its license at a daily check-in: renewals and moves need no files, and there is nothing to copy between consoles. Unbound consoles never call out and run Free; so do air-gapped ones. A server that can’t be reached stops nothing for 30 + 30 days.
  • Cloud is one stack per tenant. It is the same image as self-hosted, with no multi-tenant code and the strongest isolation.
  • Expiry and downgrades never stop patching straight away, and never delete anything. 30 days’ grace covers late invoices, read-only still scans, and what is past Free’s limits is kept and comes back with a license.

Editions

FreePro
License typesnone neededpro (self-hosted), pro_cloud (Marshal Cloud)
Customers1unlimited
Machines (servers, Linux, endpoints)25unlimited
Users3 local accountsunlimited, plus Microsoft (Entra ID) sign-in and users limited to customers
Operating systemsWindows Server, Linux servers+ Windows 10/11 endpoints (client_os)
Patchingeverything: approvals, windows, runs, reboots, health checks, rings, proxiessame
Notificationsemail (Microsoft 365, SMTP)+ Teams, Slack, webhooks (notify_chat)
Complianceoverview page+ CSV exports (exports), scheduled and branded reports (reports)
Teamaudit log page (team; it is recorded on Free too)
IntegrationsREST API and tokens (api_tokens)
Supportcommunitybusiness hours
Price€0€35 a month or €300 a year (Pro Cloud: €50 a month or €500 a year)
  • Free is always self-hosted. Pro is sold either self-hosted or for Marshal Cloud, and the two can’t be swapped: a cloud console (CLOUD=true) accepts only pro_cloud, and a self-hosted one only pro.
  • Marshal Cloud is one Marshal stack per tenant (the same image), run by us with CLOUD=true and bound to the tenant’s organisation like any console. Cloud has no Free version: unlicensed, it is read-only.
  • The FEATURES table maps tiers to feature names, and a license can also grant single extra features. A license’s max_customers field is ignored. See Building a premium feature.

Pricing

Fixed prices, set 2026-10-15:

MonthlyYearly
Pro (self-hosted)€35€300
Pro Cloud (we host it)€50€500
  • The License page shows Free and Pro with a Monthly/Yearly switch and links to the portal.
  • Online licenses are renewed on the server (their expiry moves on), so monthly plans need nothing on the console.
  • Trial: 30 days of Pro, once per organisation, from the portal.

Online licensing

A console is bound to an organisation on the Marshal website (marshal-website: portal, license server). The organisation’s licenses are assigned to its consoles in the portal, and each console picks its license up itself.

  1. On the console, Administration > License > Online licensing > Generate binding code. The console makes a new instance key (Ed25519, private key in the vault) and a 16-character code, valid 15 minutes. Only the code’s hash and the public key go to the server.
  2. In the portal, Consoles > Bind a console: the installation ID and the code.
  3. Back on the console, Bind to organisation X? Confirm (or Reject).

From then on the console checks in about every 20 hours (worker, license_checkin; Check now on the License page), signing every request with its key. Each check-in returns a lease (30 days) and the license assigned to it.

  • What is in force (licensing.state()): the license if the last lease is valid and names it; else Free. An online license runs out at the earlier of its own expiry and the lease, then the usual 30 days of grace and read-only.
  • The license goes away (unassigned or moved in the portal, revoked, the console unbound from the portal, or another copy of the console holds it): read-only at once, with the reason on the License page, until a license comes back or an admin clicks Switch to Free. Unbinding from the console itself just returns it to Free.
  • Expired past its grace (or its lease ran out): read-only, like any license. Switch to Free sets that license aside: it is ignored at check-ins until it runs longer (renewed, or a fresh lease) or another one is assigned.
  • Clock: the console never counts from a time earlier than the latest server time it has seen.
  • Copies: a console copied with its database (or restored from a backup) shares its key. The server spots it by a sequence number in the check-ins; the copy that checked in first keeps the license, the organisation is told, and binding the right console again gives it a new key.
  • Lost keys (restored without the vault key): bind again; the portal offers to let it replace the old console.
  • Sent to us: installation ID, version, and the number of customers, machines and users. Nothing else.
  • Air-gapped: ONLINE_LICENSING=false in .env (MARSHAL_ONLINE_LICENSING in the containers) stops all calls and hides binding. Such a console runs Free.
  • Server: LICENSE_SERVER_URL in .env (MARSHAL_LICENSE_SERVER_URL; empty: the website’s /api/licensing/v1). Its documents are verified against LICENSE_KEYS, so another server can’t issue licenses. LICENSE_EXTRA_KEYS (MARSHAL_LICENSE_EXTRA_KEYS) adds keys: only for a test stack with its own license server.
  • Notices: license_binding (bound, or the license from the organisation changed).

The license document

MARSHAL2.<payload>.<signature>: canonical JSON (with typ and kid) plus an Ed25519 signature, both base64url. Licenses, leases and binding statuses all use it, and a reader only accepts the typ it expects. A license holds its ID, licensee, e-mail, type, extra features, delivery (always online; anything else is refused), the installation ID, the instance key and organisation, and the issue and expiry dates.

  • Each console has an installation ID (made on first start, shown under Administration > License). A license bound to it works only on that console.
  • A license is only valid with a lease naming it, so copying it to another console does nothing. License files (pasted on the License page, or data/license/license.lic) were dropped on 2026-10-15.
  • The license key is our own Ed25519 key, separate from the agent release-signing key. Consoles know its public keys by key ID in LICENSE_KEYS (licensing.py); a new key is added there a release before it signs anything. Online, it lives only in the website’s signer container; anyone holding it can make licenses.
  • The Python source is in the image, so a determined self-hoster could patch the check. Licensing keeps honest customers honest; the license terms cover the rest.

Expiry

PhaseWhenWhat happens
Activeuntil expires_atEverything works. Admins see a banner during the last 30 days. Notices go out 30, 7 and 1 days before expiry.
Grace30 days after expiryEverything still works. Everyone sees a banner, and a license_expired notice is sent.
Read-onlyafter the grace periodYou can look, agents check in and daily scans carry on. Nothing else runs: no window or manual runs, no approvals, no config changes, no new enrollments (all answer 423). Runs already going finish. Sign-in and the License page still work.

A license that can’t be used (bad signature, another console’s, wrong hosting type) also makes Marshal read-only, with the reason shown, until it is fixed. Read-only ends when a valid license arrives at a check-in or, self-hosted only, an admin clicks Switch to Free.

The notification events are license_expiring and license_expired (and license_binding for online licensing). All are on by default for new channels. Each stage is sent once per license. For an online license the dates follow its lease too, so a console that stops reaching the license server gets the same notices before it runs out.

Windows 10/11 endpoints

Endpoints are Pro (client_os). The agent reports the Windows product type (ProductOptions\ProductType: WinNT is a workstation); older agents are recognised by their OS name. Without the feature an endpoint still enrolls, checks in and is scanned, so nothing is lost on a downgrade, but it gets no window or manual runs: the console shows it as needs Pro. It is the same agent and the same Windows Update flow as servers; updates for Windows 10/11 land in the same catalogue, and the Updates page can count only servers or only endpoints.

Free’s limits and downgrades

What is past a limit is never deleted. After Switch to Free (or on a console that updates to this version without a license), the oldest stay active and the rest wait, then come back on their own when a Pro license arrives.

  • Customers (1): a new customer over the limit is refused (402); archived customers count. The newest are archived: kept, servers still scanned, but no window or manual runs, no enrollment tokens and no new servers. Nothing is archived or restored during grace or read-only: then the admin decides.
  • Machines (25): removed servers don’t count, pending ones do. Enrolling a 26th is refused (403); a reinstall on a machine already there still works. After a downgrade the newest past 25 are held: scanned, not patched, marked needs Pro with the reason.
  • Users (3): a 4th active user is refused (402). After a downgrade only the 3 oldest active local users can sign in; the others and every Microsoft account are paused (marked on the Users page) and their sessions end.
  • Pro features: their data is kept and hidden. Teams, Slack and webhook channels are listed as paused and send nothing (they can still be switched off or deleted); the audit log keeps recording.

Upgrade caveat: an unlicensed console with more than 1 customer, 25 machines or 3 users that updates to this version archives, holds or pauses the newest ones at once. A console licensed with a license file counts as unlicensed after the update (files are no longer read): bind it to its organisation and assign its license first.

Not covered here

The premium features themselves, provisioning and billing for cloud tenants, payments in the portal (Stripe), and the EULA text (which must cover the check-in data).