Licence status
Included from Basic tier
licence_status is the resolved-entitlements payload: the effective tier,
expiry state, limits and features computed by re-verifying every stored
licence envelope against the embedded keyring and denylist, plus each stored
document's own state. One serializer (SerializeLicenceStatus) renders it
everywhere it appears:
- as the
licence_statusattribute of theGET /api/infosingleton (served beside the legacylicence_infoattribute, whose GUI contract is unchanged); - in the
PUT /api/licence201 response (beside the applied document) and theDELETE /api/licence/{licence_id}200 response; - as the
licence_statusfield ofGET /api/admin/licence(and its original mountGET /admin/licence), wrapped in the usage report below; - over API v2 as
LicenceService.GetLicenceStatus(field for field, same any-authenticated exposure; a resolution failure answersunavailableinstead of embedding an error field). Seeopschain docs api-v2.
The v2 OpenAPI document declares this resource's schema; the resource-field coverage gate holds this page to it.
Fields
| Field | Type | Meaning |
|---|---|---|
tier | string | The effective tier: free (no enterprise document — the embedded defaults) or enterprise. (pack exists only as a per-document tier, never as the resolution tier.) |
state | string | The computed licence lifecycle state — vocabulary below. With no enterprise document the Basic tier applies and state is valid (the Basic tier never expires). |
limits | object | The effective countable limits: projects, environments, assets, runners, agents (ints; -1 = unlimited), and runner_groups (optional int — absent from a licence document means unlimited). |
features | string array | The effective feature entitlements. Always present (serialized [] when empty). Fixed vocabulary: agents, assets, maintenance, templates, triggers, plus packs:<name> per signed commercial pack. |
unknown_features | string array | Granted names outside this build's vocabulary — inert, never a verification failure (issued licences may carry names a build does not gate). Absent when none. |
grace_days | int | The effective expiry grace window in days — the winning base document's own grace_days (default 30). Grace is licence-carried, never a server setting. |
documents | array | Every stored, verified, non-denylisted document's own report (below). Always present. |
invalid_documents | int | Stored documents that failed signature re-verification this resolution — they contribute nothing. Absent when 0. |
denied_documents | int | Stored documents revoked by the embedded denylist — they contribute nothing. Absent when 0. |
documents entries
| Field | Type | Meaning |
|---|---|---|
licence_id | string | The document's identity; applying a renewal with a new id accumulates beside its predecessor. |
licensee | string | Who the licence was issued to. |
tier | string | The document's own tier: enterprise or pack. |
issued | string | Issue date (plain date or RFC 3339). Absent when the document carries none. |
expires | string | Expiry (plain date or RFC 3339; a date-only value ends validity at 00:00:00 UTC that day). |
state | string | This document's own computed expiry state (same vocabulary as the top-level state), or the reporting-only state revoked (denylisted) — such a document contributes nothing whatever the dates say. |
state vocabulary
Verbatim from the code (pkg/licence/resolve.go): valid, in_grace,
expired; per document additionally revoked.
valid— before expiry.in_grace— past expiry, within the document's grace window.expired— past grace. This is the work-stop state: the server refuses starting new work (CheckWork) and the runner offer gate parks dispatch (WorkStopped), while reads and licence uploads always pass. Expiry never reverts entitlements — an expired enterprise licence keeps its limits and features, withstatecarrying the stop signal; only explicitly removing the last enterprise document reverts to the Basic tier.
When several enterprise documents are stored, the one expiring last decides
the resolution's state; pack documents layer their features beside the base
while valid or in_grace and stop contributing once expired.
Where the values come from and what changes them
Resolution re-verifies every stored envelope on each read (never a stored
validity flag), with a 15-second cache that additionally expires at the next
expiry-state boundary so a state change is observed on time. PUT /api/licence (a verified Ed25519 envelope) and DELETE /api/licence/{licence_id} change the stored set and invalidate the cache;
the clock changes state on its own.
Failure shapes: a resolution error on GET /api/info renders the attribute
as {"state": "unknown", "error": "..."}; a serialization failure renders
{"error":"licence status unavailable"}. Compositions without the
entitlement service omit the attribute entirely (the legacy shape).
The admin wrapper (GET /api/admin/licence)
The admin licence usage report embeds licence_status with:
| Field | Type | Meaning |
|---|---|---|
enforcement | string | enforced, or not wired when the composition has no entitlement service (then licence_status and usage are absent). |
store_kind | string | The server's data store: postgres or sqlite — the support bundle's licence.json carries it, so support sees at once which store the server runs on. |
licence_status | object | The payload above. |
usage | object map | Per countable limit — keys projects, environments, assets, agents, runners, runner_groups — each {"used": <int>, "limit": <int>}, counted by the same rules as the create gates (live non-deleted nodes per kind; registered unrevoked fleet members per kind; distinct runner groups). A key is absent when its count failed. |
notes | string array | Named degradations (e.g. "node usage counts are unavailable: …") rather than silent omission. Absent when none. |
Related pages
opschain docs entitlements— enforcement gates, grace and the apply / remove lifecycle.opschain docs fleet-queue— thelicence expireddispatch verdict theexpiredstate produces.