The update and license API
The endpoints Keyward Desktop and the command line themselves talk to, for people who run a mirror, automate a deployment or want to know exactly what is sent.
This is the API of this site: licenses, entitlements, the update channels and staff sign-in. Keyward Server's own API (projects, secrets, machine identities) is upstream's, documented with the server. The Keyward MCP's tools are described under AI sessions.
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. - One entitlement covers both products; each product reads its own update channel.
- 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": "1.0.0", "product": "keyward" }
hash is the SHA-256, in base64url, of the text keyward-device: followed by an identifier that the program makes at
random when it is first run. The identifier itself never leaves the computer, and is not derived from its hardware.
product is keyward (Keyward Desktop) or keyward-cli (the command line): which program asked. The two programs on
one computer share one device identifier, so they share one activation and one seat; product records the one that
asked last.
Licenses and entitlements
POST /api/v1/licenses/activate
{ "key": "KW-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-10-15T08:00:00+00:00", "expires": "2026-11-14T08: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 installations. |
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 kw-ent+jwt and the kid of the signing key. Its claims:
| Claim | Meaning |
|---|---|
iss |
https://keyward.exprezoe.com |
aud |
keyward (one audience for both products) |
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. |
Each program checks the signature by itself against the license key compiled into it. The key was generated inside a vault and has never been anywhere else; its public half:
| Key | Public key (SubjectPublicKeyInfo, base64) |
|---|---|
keyward-license-2026-10 |
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgGcvGlhcvB0PHqnXs9JRbYHRPR6ppNMqc7P2lA5KBwpFS7UIZNxmVIsBdRv58vK31FxB4UAfSLI4jIkl5iW0eg== |
The update channels
GET /api/v1/updates/{product}/{channel}/{platform}/keyward-update.json
GET /api/v1/updates/{product}/{channel}/{platform}/keyward-update.json.sig
GET /api/v1/updates/{product}/{channel}/{platform}/{installer}
With the bearer token. product is keyward or keyward-cli; 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 for that product on that channel. |
GET /api/v1/releases[?product=…]
With the bearer token. Every release the entitlement includes, of both products or of one, 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: the program sends it asmax_age).POST /api/v1/auth/identity/challengewith{ "device": { … } }answers{ "nonce": "…", "expires": "…" }.- The program 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 the programs.
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 products, the site's version and which of the above are switched on.