How Licensing Works
The license in one paragraph#
Abrq DIP is licensed with a vendor-signed JSON file placed on the host that runs the platform. The backend verifies the file's cryptographic signature at startup when running in production mode, and re-checks it once an hour in the background. There is no activation server, no phone-home, and no usage metering — verification is entirely offline against a vendor public key embedded in the release build.
The license file#
The license is a plain JSON envelope:
{
"payload": {
"license_id": "abrq-license-XXXX",
"customer": "Example Customer",
"issued_at": "2026-01-01T00:00:00+00:00",
"expires_at": "2027-01-01T00:00:00+00:00",
"features": [],
"fingerprints": []
},
"signature": "<BASE64URL_SIGNATURE>"
}
- The
signatureis an RSA-PSS signature (RSA-2048, MGF1-SHA256) over the canonical JSON ofpayload(sorted keys, compact separators). - It is verified against a vendor public key embedded in the shipped build. The matching private key never leaves the vendor — a license file cannot be minted or altered on the customer side.
- The file is not a JWT and is never uploaded through the API. It
lives on disk at the path given by
ABRQ_LICENSE_FILE(default/var/abrq-dip/license.json).
What each payload field means — and what is deliberately not in the payload (no seat caps, no pipeline caps, no capacity entitlements) — is covered in Entitlements.
Verification at startup#
With ABRQ_ENV=prod, the backend refuses to start unless the license
file exists, parses, carries a valid signature, has not expired, and
(when hardware-bound) matches this host's fingerprint. A failed check
surfaces as a startup error (License check failed: …) with an
operator-actionable message — see
Installing a license for the exact messages
and log lines.
Note. Outside prod (
ABRQ_ENV=devortest) the license check is skipped — loudly. The backend logs a WARNING eventlicense_check_bypassedwith the note that deployments must run withABRQ_ENV=prodto enforce licensing. The bypass is never silent.
After startup, an hourly background task (abrq_dip.license.recheck)
re-reads the local file, logs the outcome, and updates Prometheus gauges
(abrq_license_days_remaining, abrq_license_load_failed,
abrq_license_enforcement_active). It only ever reads the local file —
nothing is sent anywhere.
Where to see license state#
Two surfaces, same information:
- Settings → License & Backup — the License card shows the customer name, license id, issue and expiry dates, days remaining, licensed features, and whether the license is hardware-bound. Admin-only (all Settings tabs are).
GET /api/v1/license— available to any authenticated user (viewer and up). Returnsstatus(ok,dev, orerror),customer,license_id,issued_at,expires_at,days_remaining,features,is_bound, andfingerprints_count. The signature is never returned. In dev/test the endpoint returnsstatus: "dev"with all features enabled.
curl -H "Authorization: Bearer <ACCESS_TOKEN>" \
https://dip.example.com/api/v1/license
Expiry banners#
The web UI shows a non-dismissible banner as expiry approaches:
| Days remaining | Banner tier |
|---|---|
| 30 or fewer | Warning |
| 7 or fewer | Critical |
| 0 / expired (or license error) | Expired |
These banners are the warning system — there is no grace period after
expiry (a deliberate decision; see
Expiry and clock integrity). The backend also
logs license_expiry_approaching once days remaining drop below 30, and
an optional expiry alert channel can page your team — see
Notifications.
Renewal#
Renewal is a file swap, not a procedure:
- Obtain the renewed
license.jsonfrom Abrq. - Replace the file at the configured license path.
- Restart the backend API, worker, and beat processes.
The restart matters: the licensed feature set is cached for the lifetime of each process, so a replaced file is not picked up for feature gating until the processes restart. Per-path placement details are in Installing a license.