Licensing and activation
Turn the built-in 30-day trial into your purchased package, online or across an air gap, and know what the appliance does when a licence lapses.
- Version: 0.4
- Role: admin_user
- Type: task
A CID222 licence is a small signed file bound to one appliance. You export an activation request,
send it to your CID222 contact, and upload the .lic file they return. The appliance never
contacts a licence server, so the whole procedure works with no internet on either side.
What do I need?
- Licence
- Any
- Role
admin_user
Prerequisites
- The appliance has completed the first-boot setup wizard.
- You can sign in to the dashboard with a tenant that has the admin_user role.
- Settings → License shows a non-empty installation id. A licence cannot be issued against an empty one.
- The appliance host clock is correct and disciplined by NTP — licence validity is bounded by dates.
- For the air-gapped path, removable media or an email path between the appliance's operator and a machine that can reach your CID222 contact.
Which situation are you in
| Situation | What it takes |
|---|---|
| A new appliance, evaluating | Nothing. The built-in trial runs for 30 days from first boot |
| A new appliance, licensed | Activate the licence — export, send, upload |
| Evaluating a capability the trial does not include | A time-limited licence at that tier. The trial cannot be raised in place |
| Moving from Starter to Professional or Enterprise | A re-issued licence against the same installation id. Not a reinstall, and nothing restarts |
| The licence is close to expiry | The same upload, before the date rather than after |
| A page reports that a capability is not licensed | The tier does not include it |
| Creating a user is refused | A seat cap |
The product answers 402 | As a licence lapses |
| The appliance is air-gapped | Nothing changes. Every path on this page is a file exchange |
| The appliance was restored onto new hardware | Nothing, if the installation identity came back with it. A warning is expected; enforcement does not change |
What the appliance enforces
A fresh appliance runs a built-in 30-day trial at the Starter capability set, stamped from first boot rather than from the day the image was built. The trial is fully usable, but it does not unlock Professional or Enterprise features — ask your CID222 contact for a time-limited licence at that tier if you need to evaluate them.
The licence carries a tier, and the tier decides the capabilities. The appliance grants the union of the capabilities named in the file and the capabilities of its tier, so a feature added to the product after your licence was signed still works without a re-issue. Which capability belongs to which tier is generated from the code on the licence tiers reference.
| State | What it means | What the product does |
|---|---|---|
trial | No licence installed yet, inside the trial window | Runs at the trial tier, with a countdown banner |
valid | A licence verified and is inside its term | Normal operation |
expiring | Inside the warning window before expiry | Normal operation, with a banner |
grace | Past expiry, inside the grace period the licence itself names | Normal operation, with a banner |
expired / trial_expired | Past expiry and past grace, or the trial elapsed | Product routes answer 402 LICENSE_EXPIRED |
invalid | Nothing verifiable — no trust anchor, or the host clock rolled back | Product routes answer 402 LICENSE_EXPIRED |
A 402 is deliberately narrow. It blocks the product paths — chat, detection, inspection, image
and document analysis, and the key-validation endpoint the extension and the proxy call — and
leaves everything else working, including sign-in, the dashboard, the licence page and
Settings → System Updates, so the appliance can always be re-licensed without a rescue
procedure. Release images ship with
licence enforcement switched on (CID_REQUIRE_LICENSE), which means an appliance that cannot
verify a licence fails closed rather than running unlicensed.
Note
Each state change is written to the event log as LICENSE_EXPIRING, LICENSE_GRACE,
LICENSE_EXPIRED or LICENSE_TRIAL_EXPIRED, so a lapse can be alerted on in your SIEM instead
of waiting for someone to open a page.
Activate the licence
Open the licence page
Select Settings → License.
The page shows the current License state, Tier, Expires and Installation — the id your licence is bound to. On a trial it also shows how many days remain.
/assets/screenshots/license-page@0.4.pngExport the activation request
Select Export activation request.
A file named cid-activation-<installation-id>.json downloads. It carries the installation
id, the appliance's fingerprint, how strongly the identity is bound, whether the appliance
generated that identity itself, and the time it was exported — no customer data of any kind.
Send the request to your CID222 contact
Send the downloaded file to your CID222 contact from the machine you are working on.
Your contact issues a signed .lic file for your purchased package, bound to that installation
id.
Upload the licence file
Back on Settings → License, drop the .lic file on Drop your .lic file here, or select
Upload license (.lic) and pick it.
The appliance verifies the signature and the installation binding, installs the file, and the page switches to the licensed tier within a few seconds.
/assets/screenshots/license-page-activated@0.4.pngWarning
Transfer the .lic file as a binary attachment or over a checksum-verified copy, and do not open
it in an editor. A mail client that re-wraps the lines changes the bytes the signature covers,
and the appliance then rejects a genuine licence.
Renew, or move to a different package
Request the new file before the current one lapses
Send your CID222 contact the same activation request. The installation id has not changed, so the renewal binds to the same appliance.
A renewal and a package upgrade are the same operation: Starter to Professional to Enterprise is a licence re-issue, not a reinstall.
Upload it the same way
Select Settings → License, then upload the new .lic.
Capabilities expand immediately and the expiry date on the page moves out. The appliance keeps every setting and every record; nothing restarts.
Danger
Changing the appliance's installation identity invalidates the licence bound to the old one, and there is no self-service recovery — it takes a re-issued licence. Do not regenerate the identity on an appliance that already holds a licence.
When the tier does not include what you need
A capability that is not in your package answers 403 with FEATURE_NOT_LICENSED, and the
dashboard hides the surfaces it gates rather than showing a dead page. The fix is a re-issued
licence at a tier that includes it, against the same installation id — the same upload as a
renewal, and nothing restarts.
Three cases behave differently from a plain refusal:
- A capability added to the product after your licence was signed still works. Entitlements are the union of the capabilities named in the file and the capabilities of its tier, so a new key grandfathers in rather than silently revoking the module it gates.
- Code Safety on Enterprise without a GPU degrades rather than refuses. The licence page carries a warning that its machine-learning half is inactive; nothing is blocked.
- The filter-testing lab belongs to no tier. It is hidden on every licensed build, whatever you bought. Only a development appliance exposes it.
The generated licence tiers reference is the authority on which capability belongs to which package.
Seat limits
A licence may carry a seat cap. When it does, creating an account past the cap is refused with
403 and LIMIT_EXCEEDED, and the refusal is written to the event log as
LICENSE_LIMIT_EXCEEDED.
| Behaviour | Detail |
|---|---|
| What counts | The cap is on accounts, and a licence carrying both a tenant and a user limit is held to the lower of the two |
| No cap | A licence with no limit, a non-integer one, or one below 1 reads as no cap. Every licence issued before seat limits existed is in this group |
| What is never blocked | Signing in. Only the creation of a new account is refused, so a cap can never lock out the people already on the appliance |
| Where it bites unattended | Directory-driven account creation — a sync or a first sign-in that would provision a new user — fails the same way, with the same event |
| Freeing a seat | Deactivating a leaver frees one. Raising the cap is a re-issued licence |
As a licence lapses
The states are a timeline, not a switch, and every transition is written to the event log so a lapse can be alerted on in your SIEM rather than waiting for someone to open a page.
| When | State | What users see | What you do |
|---|---|---|---|
| More than 30 days before expiry | valid | Nothing | Nothing |
| The last 30 days | expiring | A banner for administrators, and a LICENSE_EXPIRING event | Request the renewal now. Your contact's turnaround is the long pole |
| Past expiry, inside the grace period the licence itself names | grace | Loud banners, a LICENSE_GRACE event. The product still works | Upload the renewal. Nothing is lost by doing it in this window |
| Past grace | expired | Product routes answer 402 LICENSE_EXPIRED | Upload the renewal. The appliance recovers within seconds of the upload |
| 30 days after first boot with no licence | trial_expired | The same 402, with LICENSE_TRIAL_EXPIRED in the log | Upload a licence |
| No trust anchor, or the clock rolled back | invalid | The same 402 | This appliance has no licence trust anchor |
Banners are shown to administrators only: a normal user cannot upload a renewal, so the banner
would give them an alarm and no action. Dismissing one is per session and per state, so a notice
dismissed while expiring returns when the appliance reaches grace.
Note
A 402 never touches sign-in, the dashboard shell, Settings → License or
Settings → System Updates. That is deliberate: an appliance must always be re-licensable
without a rescue procedure.
Keep the clock right
Licence validity is a comparison against the appliance's own clock, with no network time source to correct it. Two failure modes follow, and both are avoided by pointing the appliance at an internal time source:
- A drifting clock on an air-gapped appliance can flip a valid licence to invalid, with nothing on the network to save it.
- A clock that rolls back is treated as tampering. The appliance keeps a high-water mark; a
clock more than a day behind it puts the licence in
invaliduntil time passes the mark again. Restoring a virtual-machine snapshot from before that mark is the innocent way to hit this.
The host clock is not synchronised
You might see: The host clock is not synchronised · clock is not synchronised — if the registry login fails on a certificate error, this is why · a valid licence is reported as expired · tokens are rejected as expired immediately after sign-in
Before you start
Checks
- 1
Read whether the host clock is disciplined by a time service
diagnostics · host.timeSynced = true
- 2
Verify the appliance can reach a time source at all
expected · The status reports the clock as synchronised, or names a server the network permits.
- 3
Check whether the skew has already aged the licence out
diagnostics · appliance.license.state = "active|trial"
Causes and fixes
| Reached when | Cause | Explanation, fix and verification |
|---|---|---|
| check 1 fails | no-time-source (warning) | Licence expiry, certificate validity, session-token lifetimes and every detection timestamp are decided by this clock. A drifting clock therefore expires licences early, rejects tokens that were just issued, and makes a current certificate look not-yet-valid — three failures that each look like something else. During an install it also breaks the registry login with a certificate error that reads like a network fault. Fix: Enable time synchronisation on the appliance host and point it at a time source this network permits. Restart the gateway container afterwards so anything it cached at boot is re-evaluated against the corrected clock. Verify: diagnostics · host.timeSynced = true |
| check 2 fails | ntp-blocked (warning) | A time client that cannot reach a server leaves the clock free-running, and the failure is silent: nothing on the appliance reports that time is wrong until something dated fails. Many sites block outbound time traffic and provide an internal server instead. Fix: Point the appliance at the site's own time server, or open the time protocol to a permitted external one. See Network prerequisites. Verify: expected · The clock reports as synchronised. |
| check 3 fails | licence-mis-aged (critical) | The skew has already pushed the licence out of its validity window, so the product blocks exactly as it would for a real expiry. Requesting a renewal at this point produces a licence that will look expired too. Fix: Correct the clock first, then re-read the licence state. Request a renewal only if the state is still expired once time is right. See The licence has expired. Verify: diagnostics · appliance.license.state = "active|trial" |
Verify
- Settings → License shows License state as valid, the Tier tile naming your package, and an Expires date in the future.
- The Warnings tile is absent, or names only the soft-binding notice. A warning that the identity was generated by the appliance rather than derived from the machine is expected on a deployment where the host tooling never ran; it does not affect enforcement.
- Make one chat request. It succeeds rather than returning
402. - Open a page that belongs to your tier — the MCP servers page on Professional, SIEM export on Enterprise. It opens instead of reporting that the capability is not licensed.
If it fails
The upload is refused with a 400 naming the signature:
License upload fails with 400 "rejected: signature"
You might see: 400 rejected: signature · 400 rejected: ENOENT · License upload → 400 · licence file will not upload
Before you start
Checks
- 1
Verify the licence trust anchor exists on the host
diagnostics · appliance.license.trustAnchorPresent = true
- 2
Verify the appliance reports an installation id
diagnostics · appliance.license.installationId
- 3
Verify the host clock is disciplined by NTP
diagnostics · host.timeSynced = true
- 4
Read the licence state the gateway reports
GET /admin/license/status → {"state":"active|trial"}
Causes and fixes
| Reached when | Cause | Explanation, fix and verification |
|---|---|---|
| check 1 fails | missing-anchor (critical) | The licence-signing public key (/etc/cid/license-pubkey.pem) was never baked into this image, or was removed. Verification has nothing to check the signature against, so every licence — including a perfectly valid one — is rejected as forged. The API surfaces this as a 400 with a signature or ENOENT message rather than as a missing-key error, which is why the licence file gets blamed. Fix: Restore the trust anchor on the host and restart the gateway container, then upload the licence again. On an appliance the anchor is part of the image: an image that lacks it was built wrong and the fix is to re-run the host repair, not to hand-place a key of unknown provenance. Follow Licensing and activation; if the appliance was built from an image that shipped without the anchor, collect the support bundle and escalate rather than sourcing a key yourself. Verify: diagnostics · appliance.license.trustAnchorPresent = true If that does not apply: Attach the support bundle. The image build, not the appliance, is at fault. |
| check 2 fails | null-installation-id (critical) | The appliance reports no installation id, so no licence can have been issued for it and no licence can bind to it. This normally means the identity was never generated during first boot, or the file holding it is not readable by the gateway container. Fix: Re-run the first-boot identity step and confirm the id appears on Settings → License. Request a licence only once a stable id is shown; a licence issued against a null id is unusable. See Licensing and activation. Verify: diagnostics · appliance.license.installationId |
| check 3 fails | clock-skew (warning) | Licence validity is bounded by dates. A host clock that is wrong by more than the licence's grace window makes a valid licence appear not-yet-valid or expired, and the error surfaces at the same place as a signature failure. Fix: Correct the host clock and enable NTP or chrony, then restart the gateway container and upload the licence again. See Network prerequisites for the NTP egress the appliance needs. Verify: diagnostics · host.timeSynced = true |
| check 4 fails | wrong-licence-for-this-box (critical) | The trust anchor, installation id and clock are all correct, so the signature check is meaningful — and it fails. The licence was issued for a different installation id, was modified in transit (a mail client re-wrapping lines is the usual culprit), or was truncated. Fix: Re-download the licence file from the issuing portal, transfer it as a binary attachment or over a checksum-verified copy, and upload it again without opening it in an editor. If the installation id in the licence does not match this appliance, request a re-issue. Verify: GET /admin/license/status → {"state":"active|trial"} If that does not apply: Attach the support bundle and the installation id shown on Settings → License. |
Everything answers 402 although the wizard completed:
Every request returns 402 LICENSE_EXPIRED after the wizard completes
You might see: 402 LICENSE_EXPIRED · chat returns 402 after finishing setup · the dashboard loads but every action fails with payment required · product blocked immediately after setup
Before you start
Checks
- 1
Read whether licence enforcement is switched on for this deployment
diagnostics · appliance.license.requireLicense = true
- 2
Read the licence state the gateway resolved at boot
diagnostics · appliance.license.state = "active|trial"
- 3
Verify the licence trust anchor exists, since without it no licence can ever resolve as active
diagnostics · appliance.license.trustAnchorPresent = true
- 4
Check how long the installed licence has left
diagnostics · appliance.license.expiresAt
Causes and fixes
| Reached when | Cause | Explanation, fix and verification |
|---|---|---|
| check 3 fails | enforcement-on-without-anchor (critical) | CID_REQUIRE_LICENSE is true and the licence-signing public key is absent. The guard demands a valid licence, and verification cannot succeed without the anchor, so every licensed route answers 402 no matter what is uploaded. The appliance is enforcing a check it cannot pass. Fix: Restore the trust anchor and upload the licence, following License upload fails with 400 signature. Turning enforcement off is a deliberate, temporary measure for a lab only — never the fix on a customer appliance, because it disables the entitlement checks the tier relies on. Verify: diagnostics · appliance.license.trustAnchorPresent = true |
| check 2 fails | no-valid-license (critical) | Enforcement is on and the licence resolved to a state other than active or trial — missing, invalid or expired. The setup wizard does not install a licence, so a freshly completed appliance with enforcement on reaches exactly this state until one is uploaded. Fix: Upload the licence issued for this appliance under Settings → License. If no licence has been issued yet, request one against the installation id shown on that page. See Licensing and activation. Verify: GET /admin/license/status → {"state":"active|trial"} |
| check 4 fails | license-genuinely-expired (critical) | A licence is installed and verifies, but its validity period has ended. Enforcement then blocks the product routes while leaving administration reachable, so the appliance can be re-licensed without a rescue procedure. Fix: Upload a renewed licence. If the expiry looks wrong, check the host clock first — a skewed clock ages a valid licence out early and produces exactly this state. Verify: diagnostics · appliance.license.state = "active|trial" |
| check 1 fails | unexpected-402-without-enforcement (warning) | Enforcement is off, so the licence guard should not be blocking anything. A 402 in this state means the value the gateway read at boot differs from the value now configured — usually the container was started before the setting changed and has not been restarted. Fix: Restart the gateway container so it re-reads its configuration, then retry. If 402 persists with enforcement off, collect the support bundle: the effective configuration and the configured one disagree. Verify: diagnostics · appliance.license.requireLicense = false If that does not apply: Attach the support bundle, including the effective posture section. |
The licence has lapsed and licensed endpoints answer 402:
The licence has expired and licensed endpoints answer 402
You might see: License expired / product locked · 402 LICENSE_EXPIRED · License state is "expired" · the trial ran out
Before you start
Checks
- 1
Read the licence state the gateway resolved
diagnostics · appliance.license.state = "active|trial"
- 2
Verify the host clock is disciplined, because a skewed clock ages a valid licence out early
diagnostics · host.timeSynced = true
- 3
Verify the trust anchor exists, since without it no renewal can ever verify either
diagnostics · appliance.license.trustAnchorPresent = true
- 4
Read the licence status the API reports
GET /admin/license/status → {"state":"active|trial"}
Causes and fixes
| Reached when | Cause | Explanation, fix and verification |
|---|---|---|
| check 1 or 4 fails | expired-or-invalid (critical) | A licence is installed and its validity period has ended, or it no longer verifies. The guard then blocks the licensed routes — chat, detection, inspection, image and document analysis — with 402 while leaving administration reachable, so the appliance can be re-licensed without a rescue procedure. Fix: Upload a renewed licence under Settings → License. Request the renewal against the installation id shown on that page: a licence issued for a different id will not verify here. See Licensing and activation. Verify: diagnostics · appliance.license.state = "active|trial" |
| check 2 fails | clock-skew-fake-expiry (warning) | Licence validity is bounded by dates read from the host clock. A clock that is wrong by more than the grace window makes a current licence look expired, and the product blocks exactly as it would for a real expiry. Fix: Correct the host clock and enable time synchronisation, then restart the gateway container and re-read the licence state before requesting a renewal. See The host clock is not synchronised. Verify: diagnostics · host.timeSynced = true |
| check 3 fails | renewal-cannot-verify (critical) | The trust anchor is absent, so the renewal you are about to upload will be rejected for the same reason the current licence no longer verifies. Uploading first wastes the maintenance window. Fix: Restore the trust anchor before uploading anything. See This appliance has no licence trust anchor. Verify: diagnostics · appliance.license.trustAnchorPresent = true |
This appliance can only ever run the trial, whatever you upload:
This appliance has no licence trust anchor and can only run the trial
You might see: No license trust anchor (/etc/cid/license-pubkey.pem) · no licence can be verified on this box · the appliance only ever runs the built-in trial · every licence file is rejected, whichever one we upload
Before you start
Checks
- 1
Read whether the licence-signing public key exists on the host
diagnostics · appliance.license.trustAnchorPresent = true
- 2
Read whether licence enforcement is switched on, which decides how bad the missing anchor is
diagnostics · appliance.license.requireLicense = false
Causes and fixes
| Reached when | Cause | Explanation, fix and verification |
|---|---|---|
| check 1 fails | anchor-absent (warning) | The licence-signing public key is part of the appliance image and lives at /etc/cid/license-pubkey.pem. Verification has nothing to check a signature against without it, so every licence — including a valid one — is rejected as forged. With enforcement off the appliance still runs its built-in trial, which is why the missing anchor can go unnoticed until the trial ends. Fix: Run the host repair from Settings → System Updates, which re-establishes the files the image should carry, then re-check the snapshot. An image that still lacks the anchor after repair was built wrong: collect the support bundle and escalate rather than placing a key of unknown provenance by hand. Verify: diagnostics · appliance.license.trustAnchorPresent = true If that does not apply: Attach the support bundle. The image build, not the appliance, is at fault. |
| check 2 fails | enforcement-on-without-anchor (critical) | Enforcement demands a valid licence and the appliance cannot verify one, so every licensed route answers 402 no matter what is uploaded. The appliance is enforcing a check it is structurally unable to pass. Fix: Restore the anchor as above and upload the licence issued for this installation id. See License upload fails with 400 signature. Turning enforcement off is a lab measure only — it disables the entitlement checks the tier relies on. Verify: GET /admin/license/status → {"state":"active|trial"} |
Two more symptoms have a shorter answer:
- A page or endpoint answers not licensed (
FEATURE_NOT_LICENSED) → the capability is not in your package. See When the tier does not include what you need. - Creating a user is refused with
LIMIT_EXCEEDED→ the licence carries a seat cap and this appliance has reached it. See Seat limits.
Next steps
- Licence tiers — the generated table of which capability each package entitles.
- Naming, DNS and TLS — the names and certificate your clients need before they will talk to the appliance.
- Update the appliance — updates never change your package; the licence file is what defines capabilities.
Last updated on