Error Handling
Use this page to handle LicenseKit failures predictably in production without blurring user messaging, retries, and operator logging.
Who This Is For
- developers shipping runtime validation into an application
- backend teams building management automations
- operators creating alerts or dashboards around API failures
When To Use This
Read this page before wiring retries, user-visible license errors, or monitoring rules.
How It Works
Error envelope
Errors use a stable envelope:
json
{
"error": {
"code": "TOKEN_SCOPE_DENIED",
"message": "token scope denied",
"detail": "requires report:export"
},
"meta": {
"request_id": "req_123"
}
}Important fields:
error.codefor machine decisionserror.messagefor a readable summaryerror.detailfor extra contextmeta.request_idfor logs, support, and escalation
Common runtime failures
Typical runtime-side errors include:
LICENSE_EXPIREDLICENSE_INACTIVEACTIVATION_LIMIT_EXCEEDEDFEATURE_QUOTA_EXCEEDEDDEVICE_BLACKLISTED
Recommended handling:
- map the machine code to an application action
- show users a short actionable message
- log the code and
request_id - avoid exposing raw internals in the UI
Common management failures
Typical management-side errors include:
TOKEN_SCOPE_DENIEDINVALID_QUERYVALIDATION_ERRORNOT_FOUNDIDEMPOTENCY_CONFLICT
Recommended handling:
- for scope errors, inspect
x-required-scopes - for validation errors, fix the request payload rather than retrying blindly
- for idempotency conflicts, reuse the original body or generate a new key for a new logical action
Retry rules
Safe retry behavior depends on the operation type:
- idempotent or idempotency-keyed writes can be retried with backoff on retriable failures
- pure reads can be retried on transport or server failures
- do not retry arbitrary 4xx responses as if they were transient
Good default retriable categories:
- network transport failures
4295xx
Logging and monitoring
Always log:
error.codemeta.request_id- operation name
- relevant resource ids
Track these patterns separately:
- scope denial spikes
- idempotency conflicts
- runtime expiry/inactive patterns
- repeated server-side failures
Example
Reasonable TypeScript runtime handling:
ts
try {
const result = await runtime.validateLicense({
body: { fingerprint }
});
const publicKeys = await system.listPublicKeys();
const verification = await verifyRuntimeResult(
result,
new PublicKeyStore(publicKeys.data)
);
if (!verification.ok) {
throw new Error("runtime signature verification failed");
}
} catch (error: any) {
logger.error("license validation failed", {
code: error.code,
detail: error.detail,
requestId: error.meta?.request_id
});
if (error.code === "LICENSE_EXPIRED") {
showMessage("Your license has expired. Renew to continue.");
} else if (error.code === "ACTIVATION_LIMIT_EXCEEDED") {
showMessage("This license is already activated on the maximum number of devices.");
} else {
showMessage("License validation failed. Please try again or contact support.");
}
}Common Mistakes
- branching on human-readable
messageinstead of stablecode - retrying all 4xx responses as if they were transient
- showing raw backend details directly to end users
- dropping
request_idfrom logs and then losing the easiest correlation handle - treating signature verification failures as ordinary license denials