The update and license API
The endpoints PluggedDesk itself talks to, for people who run a mirror, automate a deployment or want to know exactly what is sent.
Conventions
- The API is under
/api/v1, over HTTPS only. Every answer is JSON, except the installer itself. - An installation proves its entitlement with
Authorization: Bearer <entitlement>. Cookies are not read by the API. - A refusal has this shape, with a status code that matches:
{ "ok": false, "error": "seat_limit", "message": "All 3 seat(s) of this license are in use (…).", "ref": "9f2c41aa" }
error is stable and meant for programs. message is meant for people. ref, when present, is the reference of the
line in the site's audit trail.
- Requests are limited per network address. Over the limit, the answer is
429with aRetry-Afterheader.
An installation describes itself
Several requests carry a device:
{ "hash": "…43 characters…", "name": "OPS-LAPTOP-7", "platform": "win-x64", "version": "0.5.0" }
hash is the SHA-256, in base64url, of the text pluggeddesk-device: followed by an identifier that PluggedDesk makes
at random when it is first run. The identifier itself never leaves the computer, and is not derived from its hardware.
Licenses and entitlements
POST /api/v1/licenses/activate
{ "key": "PD-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX", "device": { … } }
Activates the installation: a new seat, or the seat the installation already has. Answers:
{
"ok": true,
"token": "eyJhbGciOiJFUzI1NiIs…",
"entitlement": {
"id": "act_…", "kind": "license", "licensee": "…", "organization": "…",
"plan": "early-access", "planTitle": "Early access", "channels": ["stable"],
"issued": "2026-09-29T08:00:00+00:00", "expires": "2026-10-29T08:00:00+00:00",
"seats": { "used": 1, "total": 3 }
}
}
error |
Status | Meaning |
|---|---|---|
license_unknown |
403 | The key is not known, or is mistyped. |
license_revoked |
403 | |
license_expired |
403 | |
seat_limit |
409 | Every seat is in use. The message names the computers. |
licensing_unavailable |
503 | The site has no signing key configured. |
GET /api/v1/entitlements/current
With the bearer token. Says what the entitlement is today, from the site's records.
POST /api/v1/entitlements/refresh
With the bearer token, and { "device": { … } }. Answers like activate, with a new token. An expired token cannot be
refreshed: activate again.
POST /api/v1/entitlements/deactivate
With the bearer token. Gives the seat back. The token stops working at once.
error |
Status | Meaning |
|---|---|---|
entitlement_expired |
401 | Activate again. |
entitlement_invalid |
401 | Not a token this site signed, or not for this site. |
entitlement_revoked |
401 | The installation was deactivated. |
The entitlement token
A JWS in compact form, signed with ES256. Its header has typ pd-ent+jwt and the kid of the signing key. Its claims:
| Claim | Meaning |
|---|---|
iss |
https://pluggeddesk.exprezoe.com |
aud |
pluggeddesk |
sub |
The activation. |
jti |
This token. |
iat, nbf, exp |
Issued, not valid before, expires. |
kind |
license or staff |
lic, name, org, plan |
The license, the licensee, the organisation, the plan. |
chn |
The release channels. |
dev |
The installation's hash. |
PluggedDesk checks the signature by itself against this key, which is compiled into it:
| Key | Public key (SubjectPublicKeyInfo, base64) |
|---|---|
pluggeddesk-license-2026-09 |
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEyrz3xvrDYdbLn+DjQnc/yZ7od1pWDuzVZOTn8CDlRKb/D/G9HczymXXvEB5Tnt3ofINjCvPizE2zfxGsiK0tag== |
The update channel
GET /api/v1/updates/{channel}/{platform}/pluggeddesk-update.json
GET /api/v1/updates/{channel}/{platform}/pluggeddesk-update.json.sig
GET /api/v1/updates/{channel}/{platform}/{installer}
With the bearer token. channel is stable or preview; platform is win-x64.
The manifest is served byte for byte as it was signed. It names its installer by file name only, so the installer is
read from beside the manifest, and a mirror can serve a release as it is. Both small files carry an ETag; the
installer supports ranges, so an interrupted download can be resumed.
error |
Status | Meaning |
|---|---|---|
channel_not_allowed |
403 | The entitlement does not include that channel. |
no_release |
404 | Nothing has been released on that channel. |
GET /api/v1/releases
With the bearer token. Every release the entitlement includes, newest first.
Staff sign-in
For people at Exprezoe, in place of a license key.
GET /api/v1/auth/identitysays whether staff sign-in is on, names the issuer and the client to sign in with, and says how recent the sign-in at the issuer must be (maxAge, in seconds: PluggedDesk sends it asmax_age).POST /api/v1/auth/identity/challengewith{ "device": { … } }answers{ "nonce": "…", "expires": "…" }.- PluggedDesk signs in at the issuer in the system browser (authorization code with PKCE), with that nonce.
POST /api/v1/auth/identity/exchangewith{ "id_token": "…", "device": { … } }answers like activate.
The site accepts an ID token only when its nonce is a challenge the site issued to that installation and has not seen
used. It then checks the signature against the issuer's published keys (RS256 only), the issuer, the audience, the
lifetime, that the token is fresh, that the sign-in at the issuer is no older than maxAge (auth_time), that a
second factor was used, and that the person is in a group that may use PluggedDesk.
When the issuer has changed its key, the first token signed with the new one is checked against the keys the issuer publishes at that moment. A token that names a key the issuer does not publish costs the site at most one read of the issuer's keys in thirty seconds.
error |
Status | Meaning |
|---|---|---|
identity_disabled |
503 | Staff sign-in is not switched on. |
challenge_unknown |
401 | The sign-in was not started with this site, or took longer than ten minutes. |
token_replayed |
409 | That sign-in was already used. |
token_invalid |
401 | The ID token could not be verified. |
stale_authentication |
401 | The sign-in at the issuer is older than maxAge, or the token does not say when it was made. |
mfa_required |
403 | No second factor was used. |
not_in_required_group |
403 | |
issuer_unavailable |
503 | The issuer's keys could not be read. |
What the site is
GET /api/v1/health answers { "ok": true } when the site and its records are reachable.
GET /api/v1/meta names the site's version and which of the above are switched on.