Skip to main content
Version: v0.0.34

License Activation

From v0.0.31 Honeyframe ships with a per-org license gate. Each license is a vendor-issued, Ed25519-signed JSON document that names the customer org, the licensed tier, the licensed products, an expiry date, and an optional user limit. The backend validates the signature on every request through license_gate_middleware; an expired or invalid license returns 403.

License enforcement is off by default — installs without a license.json (and without LICENSE_ENFORCE=1) skip the gate entirely. This is intentional so internal test installs and demos work out of the box.

Activation paths

There are two ways to install a license. They produce the same end state — pick whichever fits your workflow.

1. At install time

Pass --license PATH to setup-customer.sh, or set the license.file: field in install.conf:

./iaas/scripts/setup-customer.sh --config install.conf --license ./acme.license.json
# install.conf
license:
file: ./acme.license.json

The script copies the file to $DATA_DIR/license.json (chmod 600) and writes LICENSE_FILE=$DATA_DIR/license.json into each tier's .env. The backend picks it up on next start.

2. From the Platform UI

Sign in as an admin and open Settings → License. The page shows the current license status and a paste box for a new license key. Submitting it POSTs to /api/license; the backend writes the new license to $DATA_DIR/license.json, invalidates the in-process cache, and the new state takes effect immediately — no restart.

This path is what your customers use to renew without you needing shell access.

License status

GET /api/license returns the current state. The Settings page reads it on load:

StatusWhat it meansUI badge
validActive, signature checks out, not expired.Green — "Active".
graceRecently expired but inside the configurable grace window. Backend still serves traffic; UI shows a warning.Amber — "Grace Period".
expiredPast grace. License-gated endpoints return 403.Red — "Expired".
missingNo license.json on disk and no LICENSE_KEY env. Gate is off (or 403 if LICENSE_ENFORCE=1).Gray — "No License".
invalidSignature mismatch or malformed payload. Gate fails closed.Red — "Invalid".

The response payload includes customer, tier (starter / professional / enterprise), expires, days_remaining, users_limit, products (e.g. hub_platform, hub_intel_healthcare, hub_cloud), and features (a list of feature flags scoped to the license).

Per-org vs instance-wide

Resolution order on every request:

  1. Per-org licensehoneyframe.org_licenses row keyed by the request's org_slug. This is the Dataiku-style per-org activation that lets one Honeyframe install serve many independently-licensed orgs. Authoritative since v0.0.30.
  2. Legacy instance-wide license — falls back to LICENSE_KEY env var or $DATA_DIR/license.key on disk. Single-tenant installs typically only have this. (The pre-v0.0.32 fallbacks at /opt/honeyframe/license.key and /opt/dataintel/license.key were dropped in v0.0.32 — DB licensing has been the source of truth since v0.0.30, and the install-dir paths got destroyed by binary swaps on upgrade.)

When both exist, per-org wins for that org. Other orgs on the same install continue using the instance-wide license.

Bypass paths

The license gate has an allowlist of routes that always succeed regardless of license state — login, the license upload endpoint itself, and the health check. This is what lets you upload a renewal license when the current one has expired without getting locked out.

If you do get locked out (e.g. corrupt license.json and no shell), drop the file:

rm /data/honeyframe/license.json
systemctl restart hub-platform

The gate falls back to "missing" — the install runs unlicensed until you upload a new key from the UI.

Renewal

Two ways:

  • UI — paste the new key into Settings → License. Takes effect immediately.
  • CLI — overwrite $DATA_DIR/license.json and restart the service. The backend caches the parsed license; either restart or hit POST /api/license to invalidate the cache.

The grace period exists so a renewal that arrives a day or two late doesn't take production down.

Feature flags

Some product surfaces — typically the chat connector, the AI dashboard generator, and certain enterprise-only connectors — check LICENSE_FEATURES before serving. If your tier includes a feature, its flag appears in the features: array of the license. If not, the corresponding endpoint returns 403 feature_not_licensed.

The exact feature catalog ships with each release; the License page in the UI lists what's enabled for your install.

Disabling the gate

For dev environments, set LICENSE_ENFORCE=0 in the backend's .env (or just leave LICENSE_FILE unset). The middleware still loads but every request short-circuits as valid. Do not do this in production — there is no audit trail of unlicensed access once the gate is off.

Troubleshooting

SymptomLikely causeFix
403 license_invalid on every requestlicense.json signature mismatch (corrupted or wrong customer).Re-download from the vendor portal; replace $DATA_DIR/license.json.
403 license_expiredLicense past expiry + grace.Renew via UI or replace the file.
Settings → License shows "No License" but you uploaded oneCache not invalidated.Restart hub-platform or re-upload through the UI (which calls invalidate_org_license_cache).
feature_not_licensed on a known-good endpointThe feature requires a higher tier.Check the License page's "Licensed Features" list; contact the vendor for an upgrade.
Locked out after a bad uploadlicense.json is malformed; gate fails closed.rm $DATA_DIR/license.json && systemctl restart hub-platform, then upload again from the UI.

Where the validation logic lives

For reference (you don't need to touch this — it's compiled into the binary):

  • paas/backend/services/license.py — signature verification, status decoding, cache.
  • paas/backend/middleware/license_gate.py — request-time enforcement, allowlist.
  • paas/backend/routers/license.pyGET/POST /api/license.
  • scripts/generate_license.py (vendor-side, not in the customer tarball) — Ed25519-signs new license JSONs.