Skip to main content
The install file says who the install is and never expires. The activation file says what it may do, and it is the only file you renew.

The two files

The xpander backoffice issues both together, with the same license_id, and xpander binds them at runtime. Renewal re-issues only activation.json, so a lapsed term never blocks a helm upgrade, and your registry mirror and org stay untouched.
This is the xpander-license/v2 format. Older installs with one signed license.json (xpander-license/v1) keep working through --license-file. See Legacy single-file license.

Sealed and opaque

Each file is a small JSON envelope around one encrypted blob:
The blob is signed with an Ed25519 private key (cannot be edited) and then sealed with libsodium crypto_box_seal (cannot be read). The xpander images carry the keys to open and verify it. Editing the blob invalidates it, and each file is rejected in the other’s slot. Because the installer cannot read the org id, you pass it with --org. The backoffice shows it as ORGANIZATION_ID, and services verify the match at boot.

Install with the two files

From a terminal with kubectl access to the cluster, run the command the backoffice gives you:
The installer checks both envelopes (one of each kind, not swapped), takes the org from --org, and creates the xpander-license Secret with both files. All three flags are required. The rest of the install is unchanged: see Air-gapped deployment.

Deploying the chart directly (Argo CD / Helm)

With GitOps, Argo CD or Flux, create the Secret yourself:
Then set the license values:
Setting installKey and activationKey selects two-file (v2) mode. Leaving both empty falls back to the legacy global.license.key (license.json). Do not set both modes.

What the license enforces

Seat and agent caps

max_seats and max_agents are enforced. Unset means unlimited. Creating the N+1th user or agent is refused, with a message on how to raise the cap (re-activate). Everything already created keeps working, even after a smaller activation file is swapped in.

Expiry and grace

Expiry blocks only new tasks. Nobody is logged out and nothing running stops. GET /v1/license/status, with a signed-in user’s bearer token, returns state (valid, expiring, grace or expired), the deployment and the granted features. Alert on anything other than valid or grace to catch a missed renewal.
Feature grants (features, plus those the Air-gapped deployment implies, such as custom models, BYOK, SSO and custom skills) also come from the activation file. An ungranted feature is refused with a pointer to re-activation.

Capabilities

The License page’s Install capabilities card lists each chart-owned capability (voice, text-to-speech, email delivery, actions, code runner, the MCP endpoint) as Enabled or Disabled, with the Helm value that sets it, for example global.mcp.publicUrl: https://<your MCP host>. They are switched in the chart, not on this page.

Renewing: re-activate

Renewal (term, caps or features) re-issues only the activation file. Staff use the Re-activate action on the license row in the backoffice.
1

Get the new activation.json

From your xpander contact. It carries the same org_id and license_id as your install.
2

Swap it into the Secret

Replace only the activation.json key:
3

Restart xpander

Roll the services that read the license:
The billing Deployment exists on chart 0.9.0 and later. Leave it out on an older chart.Or redeploy: bash install.sh upgrade, or your pipeline’s re-install with the same values, pins and license flags (declarative re-install). A checksum annotation on the Secret rolls the services.
Passing your own --values to the installer? Your values file displaces the global.license block the installer writes. Add --set global.license.installKey=install.json --set global.license.activationKey=activation.json --set global.license.publicKey=<license public key> to every install, re-install and renewal command that carries --values.
The new activation file must name the same org and license_id as the install file, or xpander refuses it.

Staff: issuing a license

In the xpander-app admin area (staff-only), Issue a license gives one handoff view: install.json and activation.json, the install command prefilled with the org id, ORGANIZATION_ID for --org, and the License public key for global.license.publicKey. Re-activate later issues a fresh activation.json only (same org, same license_id, new expiry / caps / features).

Legacy single-file license

Older installs use one signed license.json (xpander-license/v1), installed with --license-file ./license.json and wired through global.license.key. It still works: see The License. Whether installKey and activationKey are set selects the mode.

Next steps

Install xpander Air-Gapped

The full install flow.

Review the Air-Gapped security architecture

Zero egress, signed license, local sign-in.