License Server — Client API Reference
Public REST API that client applications and the Node.js SDK call to activate licenses, validate status, and refresh policy tokens.
Base path:
/v1/client— no admin JWT required. Authentication uses license key + product ID (slug or UUID).
Endpoints
| Method | Path | Description | Used by SDK? |
|---|---|---|---|
| POST | /v1/client/activate | First-time license activation, create/record instance, return policy token | Yes — first boot |
| POST | /v1/client/check | Periodic license validation, return new policy token if active | Yes — background sync |
| POST | /v1/client/refresh | Alias of check on the server | Yes — license.refresh() |
| POST | /v1/client/status | Fast status read, does not return policy token | No — custom clients |
Note: Business errors (revoked, expired, etc.) return HTTP
200withok: false. Only rate limiting returns HTTP429.
POST /v1/client/activate
Request
{
"productId": "mediahub",
"licenseKey": "CL1.xxxxx.CL-AAAAA-BBBBB-CCCCC-DDDDD-EEEEE",
"instanceId": "sha256_hex...",
"application": {
"domainHash": "sha256-of-normalized-domain",
"environment": "production"
},
"sdk": {
"name": "@corelicense/node",
"version": "0.0.0",
"runtime": "node",
"runtimeVersion": "v22.0.0",
"appVersion": "1.0.0"
}
}
application is only sent during activate. The SDK sends domainHash (hash of normalized domain) and environment from NODE_ENV — plain domain is never sent.
Response (success)
{
"ok": true,
"status": "active",
"policyToken": "eyJhbGciOiJFZERTQSJ9...",
"policyVersion": "12",
"serverTime": "2026-06-07T12:00:00Z",
"nextCheckAfter": 21600,
"apiUrl": "https://api.corelicense.net"
}
Response (error)
{
"ok": false,
"status": "revoked",
"code": "LICENSE_REVOKED",
"message": "License has been revoked"
}
message may include an admin-provided reason (suspend/revoke/block). Example for blocked instance: status: "blocked", code: "INSTANCE_BLOCKED".
POST /v1/client/check
Request
{
"productId": "mediahub",
"licenseKey": "CL1.xxxxx.CL-AAAAA-BBBBB-CCCCC-DDDDD-EEEEE",
"instanceId": "sha256_hex...",
"currentPolicyHash": "sha256...",
"sdk": {
"name": "@corelicense/node",
"version": "0.0.0",
"runtime": "node",
"runtimeVersion": "v22.0.0",
"appVersion": "1.0.0"
}
}
Response (success)
{
"ok": true,
"status": "active",
"policyToken": "eyJhbGciOiJFZERTQSJ9...",
"policyVersion": "12",
"serverTime": "2026-06-07T12:00:00Z",
"nextCheckAfter": 21600,
"apiUrl": "https://api.corelicense.net"
}
Response (error)
{
"ok": false,
"status": "revoked",
"code": "LICENSE_REVOKED",
"message": "License has been revoked"
}
POST /v1/client/status
Request body is the same as check, but the server does not sign a policy token and does not record new instances. A successful response only includes ok, status, apiUrl, serverTime, nextCheckAfter, and policyVersion.
License status values
| Status | Meaning |
|---|---|
active | License is valid with full entitlements |
suspended | Temporarily suspended by admin |
revoked | Permanently revoked |
expired | Past expiration date |
invalid | Invalid key, not yet started, or max instances exceeded |
blocked | Instance blocked (may include block reason) |
Error codes
| Code (server) | SDK mapping | Description |
|---|---|---|
PRODUCT_NOT_FOUND | PRODUCT_NOT_FOUND | Product ID/slug does not exist |
INVALID_PRODUCT | PRODUCT_NOT_FOUND | Legacy alias |
LICENSE_NOT_FOUND | LICENSE_NOT_FOUND | License key does not exist |
LICENSE_INVALID / INVALID_LICENSE | LICENSE_INVALID | License does not belong to specified product |
LICENSE_REVOKED | LICENSE_REVOKED | License has been revoked |
LICENSE_SUSPENDED | LICENSE_SUSPENDED | License has been suspended |
LICENSE_EXPIRED | LICENSE_EXPIRED | License has expired |
LICENSE_NOT_STARTED | LICENSE_NOT_STARTED | License effective date not yet reached |
POLICY_SIGN_FAILED | POLICY_SIGNATURE_ERROR | Server failed to sign policy token |
MAX_INSTANCES | MAX_INSTANCES | Max instances exceeded (status invalid) |
INSTANCE_BLOCKED | INSTANCE_BLOCKED | Instance blocked by admin (status blocked) |
RATE_LIMITED (HTTP 429) | RATE_LIMITED | Too many requests (retryable) |
Public keys
The SDK verifies policy tokens using Ed25519 public keys. During boot(), the SDK calls the product keys endpoint (slug or UUID) and caches keys to public.keys.json:
GET /v1/public/keys/{kid}
GET /v1/public/products/{productId}/keys
Response:
{
"keys": [
{ "kid": "key_dev_01", "alg": "Ed25519", "publicKey": "..." }
]
}
HTTP errors (public keys)
| HTTP | code | Description |
|---|---|---|
| 404 | PRODUCT_NOT_FOUND | Product does not exist |
| 404 | PUBLIC_KEYS_NOT_PUBLISHED | Product exists but signing key not published |
| 404 | PUBLIC_KEY_NOT_FOUND | GET /v1/public/keys/{kid} — kid not found |
| 429 | RATE_LIMITED | Too many public API requests |
The API domain for the SDK is embedded in the license key (format CL1.<base64url>.CL-...) when admin creates/rotates keys. Server configures via SDK_PUBLIC_URL. Check/activate responses also return apiUrl for SDK caching with legacy keys.
GET /v1/public/sdk-config
# Response
{ "apiUrl": "https://api.corelicense.net" }
When admin rotates signing keys, the SDK automatically refetches keys and retries verification once. Pass publicKey in config to pin a key — pinned mode disables auto-fetch.
Minimal data policy
The SDK only sends:
- productId, licenseKey, instanceId
- SDK version, app version (if available)
- Policy token hash (check/refresh)
- Application domain hash + environment (activate)
The SDK does not send: database contents, user data, request bodies, internal logs, secrets, or business data.
Health check
GET /v1/public/health