Appearance
Licences and the Community edition
reportd checks a signed licence file offline, and grants what the licence lists. Without one, it runs as Community.
What Community includes
| Community has | Community leaves out |
|---|---|
| 5 templates, 1 sub-report per template, 3 designer seats | The assistant |
| Passwords with MFA and passkeys | Sign-in providers (OIDC and SAML) |
| MCP agents and API keys, and approvals of agents' publishes | More than one environment, and promotion |
| The viewer and report links | Postgres state, separate renderd instances and job webhooks ("scale") |
| Branding: your own logo, colours, product name and sign-in page | |
| The example gallery, and importing reports from files | Template sources of your own: git repositories, folders and galleries |
| Batch jobs without webhooks, libraries, the trash, backups and migrations | |
| Every output format and PDF standard |
Community output looks like any other: there is no "Made with Reportforge" mark.
A licence lists features and limits:
| Feature | Grants |
|---|---|
sso | OIDC and SAML providers |
environments | Publishing outside the first environment, and promoting templates, sources and libraries between them |
assistant | The studio's AI assistant |
scale | state.driver: postgres, renderer.urls and renderer.discover, and job webhooks |
branding | The install's own logo, favicon, colours, product name and sign-in page, in the studio and the viewer |
template-sources | Template sources of the organisation's own, which designers copy new reports from: git repositories, folders and published galleries (template-sources.md) |
| Limit | Counts |
|---|---|
templates | Live templates; trashed ones don't count |
subreportsPerTemplate | The sub-reports a template's rfml.lock.json pins |
designerSeats | Users, not disabled, whose roles (granted or mapped from an IdP) allow template.write in any environment. Viewers and API keys take no seat. |
A limit the licence leaves out is unlimited; a limit of 0 means none. A licence's edition ("Professional") is a label to show: reportd never checks it.
Installing a licence
A licence is a text block:
text
-----BEGIN REPORTFORGE LICENCE-----
eyJ2IjoxLCJpZCI6ImxpY18wMUo…
.
Wm9tZVNpZ25hdHVyZQ…
-----END REPORTFORGE LICENCE-----Install it any one of these ways:
The studio: the account menu's Licence (administrators,
user.manage), then paste it or choose its file, and Install.The setup wizard's licence step, or
reportd setup --licence-file <file>.The API:
PUT /v1/licencewith the block as the body, or{"licence": "<block>"}.DELETE /v1/licenceremoves it.The CLI, on a stopped reportd:
reportd licence install <file>,reportd licence show,reportd licence remove. Onstate.driver: postgresthey work while reportd runs.The
licencesetting (REPORTD_LICENCE): the block, or asecret:reference to it. It wins over one installed any other way, and the studio then shows it as fixed. On Helm, mount it from a Secret:yamlsecrets: - secretName: report-forge-licence key: licence file: licence config: licence: secret:file:licence
reportd refuses a licence that doesn't verify, is signed by a key it doesn't know, hasn't started, or has lapsed, and says why. A licence applies at once, without a restart. It is kept in dataDir/licence, or in the licence table on Postgres, so every node sees it. Installs and removals are audited as licence.install <id> and licence.remove <id>.
GET /v1/licence shows anyone with studio.access the phase, features, limits and use. Administrators also see the licensee, the id, the binding, the history, the warnings and what is frozen.
Phases
| Phase | When | What happens |
|---|---|---|
community | Never had a licence | Community's features and limits |
transition | An install from before licences, for 60 days | Every feature and no limits, with a banner |
active | Before a licence's last 30 days | What the licence grants |
expiring | Its last 30 days | The same, with a banner and a daily log warning |
grace | The 30 days after it expires | The same, with a stronger banner |
lapsed | After the grace or the transition, or with the licence removed | Community's limits, with what is in use carried on (below) |
reportd checks the phase at start and every hour. Administrators see a banner in the studio in the transition, expiring, grace and lapsed phases, and on a binding mismatch, with the Licence dialog a click away.
The clock
Expiry is checked against the latest of the system clock and the newest time in the install's own data: dataDir/licence-clock (reportd writes the time there every hour), the newest audit entry (across the rotated logs, and on Postgres the audit_events table, so rotation can't move the clock back), the newest commit on an environment branch, and the newest draft edit and job in the template store. So:
- setting the system clock back doesn't bring an expired licence back;
- editing or deleting
licence-clockdoes nothing while the data holds later times; - a system clock more than a day behind the data is a warning in the log and the studio. New records still get the system's time.
Installs from before licences
The release that brings licences takes dataDir to format 6 (migrations.md). On an install that already has users or templates, that step records a 60-day transition in dataDir/licence-history (and, on Postgres, in the schema's licence table, from the users there). For those 60 days every feature works and nothing is limited, and:
- administrators see a banner with the days left, and what Community would do: which templates it would freeze, how many designers over 3, and which configured features need a licence;
- the log says the same at start and daily.
After the 60 days the install is lapsed, as below. Installing a licence at any point ends the transition. The end date is checked against the latest time, so turning the clock back doesn't extend it.
A new install, with an empty dataDir, records no transition and is Community from its first start.
Restoring a backup from before the upgrade starts a new transition, because the restored data holds no record of the first, and nothing else can tell. It costs everything since that backup, so it isn't a practical way round the limits.
Lapses and features in use
A lapse never stops reportd or locks anyone out. It stops growth: new templates past the limit, more designers, new people through SSO.
A feature in use keeps working after a lapse only if a licence this install had granted it, or the install had the 60-day transition. A feature no licence of the install granted is treated as on an install that has never had a licence.
| Feature | Lapsed, and a licence the install had granted it | Never granted |
|---|---|---|
| SSO | Providers keep signing in the people they already sign in. JIT makes no one, no invite binds, and no identity is linked (the sign-in page says a licence is needed). | Needs a licence with sso while auth.oidc or auth.saml has providers. |
| Environments | Every environment keeps rendering what it has. Publishing, rollback and library releases work only in the first environment; promoting is refused. An approval that would publish elsewhere stays pending. | The same, and reportd logs at start which environments are ignored. |
| Assistant | Off at once | Off |
| Postgres state | Keeps working, with a warning | Needs a licence with scale while state.driver is postgres. |
| Separate renderd | Keeps spreading renders, with a warning | Needs a licence with scale while renderer.urls or renderer.discover is set. |
| Webhooks | Existing endpoints keep delivering. A new job can't name a callbackUrl. | jobs.webhooks is ignored, with a warning. |
| Branding | Keeps showing, frozen: it can't be changed until a licence grants branding again. | The defaults show, and branding can't be set. |
| Template sources | Kept as they were set up, but off: designers see only the example gallery. They come back on with a licence that grants template-sources. Reports copied from them are untouched. | Only the example gallery can be turned on or off; adding a source is refused. |
dataDir/licence-history (and, on Postgres, the schema's licence_seen table) remembers each licence installed and the features it granted. It survives restores of later backups, and removing a licence never clears it. So:
- a licence with
scalethat lapses, or is replaced by one withoutscale, leaves Postgres state, separate renderd instances and the webhooks working; - a licence without
scalenever unlocks them, whether it is installed, in its grace, or lapsed; - the transition counts as granting every feature, for good;
- licences recorded before reportd recorded features count as granting every feature, so an install upgraded with one keeps what it has.
The break-glass administrator's password always signs in (sso.md).
A configuration that needs a licence
An install with SSO providers, Postgres state or separate renderd instances configured, and no licence now or in its history that granted the feature (sso or scale), still starts. That is an install that has never had a licence, or one whose licences didn't grant the feature. It logs what it needs (state.driver postgres needs a licence with scale, for example), then asks for the licence:
- In setup mode (no users yet), the setup wizard's licence step needs a licence that grants what the configuration needs. "Continue with Community" isn't offered, and setup can't finish without one.
reportd setupchecks the same. - With users (for example a Community install that has since been given
state.driver: postgres), only signing in,GET /v1/me,GET /v1/info,POST /v1/logoutand/v1/licencework. Every other request answers 503licence-needed, naming what is needed. The studio shows "This install needs a licence", and administrators install one from it. Once a licence is installed, everything works at once, without a restart.
The licence can also be given before the first start: the licence setting (REPORTD_LICENCE), or reportd licence install on a stopped reportd.
PUT /v1/licence, reportd licence install and the Licence dialog accept any licence that verifies, but one that doesn't grant what the configuration needs changes nothing: the install keeps answering licence-needed until a licence with the feature is installed. Once one is, the feature keeps working after that licence lapses, as above.
Limits
Each refusal is 409 licence-limit:
json
{"error": "licence-limit", "limit": "templates", "allowed": 5, "used": 5,
"message": "Community allows 5 templates, and this install has 5. Delete a template, or install a licence that allows more."}and a missing feature is 403 {"error": "licence-required", "feature": "assistant", "message": …}. The studio says what a licence unlocks where it offers these: the New report button, the assistant panel, Promote and publishing outside the first environment.
- Templates are checked when one is created (a draft of an existing template in another environment isn't new), restored from the trash, or seeded; seeding stops at the limit and logs what it skipped.
- Over the limit, after a downgrade or a lapse, the oldest templates, by their first draft edit, stay editable; the rest are frozen. A frozen template's drafts can't be saved, and it can't be published or promoted, but every published version keeps rendering. The catalog marks it Frozen, and a designer who opens it sees why. Deleting templates, or installing a licence, unfreezes them at once.
- Sub-reports: a publish (dry runs too) or a promote whose lock pins more sub-reports than
subreportsPerTemplateis refused. Versions already published keep rendering. - Seats: creating a user, granting a role, enabling a user or giving a custom role
template.writeis refused when it would make a designer pastdesignerSeats. Existing designers keep their roles when the seats shrink; removing or disabling designers frees seats. An SSO sign-in whose mapped roles would make a new designer past the seats still signs in, without the roles that allowtemplate.write; it is audited aslicence.seat-refused <name>, and the studio tells the user.
Binding
A licence can name an installation id and domains. A mismatch is a warning in the log and the studio, never a refusal.
- The installation id is made at first start and kept in
dataDir/installation-id(on Postgres, shared by the nodes). Backups include it, so a restore keeps it.reportd licence showand the Licence dialog show it; send it with a licence request. - Domains are checked against
publicURL's host;*.example.comcovers its subdomains.
Files
| File | Holds |
|---|---|
dataDir/licence | The installed licence (state.driver file) |
dataDir/licence-history | The licences installed, with the features each granted, and the transition |
dataDir/licence-clock | The latest time, written hourly |
dataDir/installation-id | The installation id |
Backups include all four. On Postgres the licence, the history and the installation id are also in the schema's licence and licence_seen tables.