Skip to content

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.code for machine decisions
  • error.message for a readable summary
  • error.detail for extra context
  • meta.request_id for logs, support, and escalation

Common runtime failures

Typical runtime-side errors include:

  • LICENSE_EXPIRED
  • LICENSE_INACTIVE
  • ACTIVATION_LIMIT_EXCEEDED
  • FEATURE_QUOTA_EXCEEDED
  • DEVICE_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_DENIED
  • INVALID_QUERY
  • VALIDATION_ERROR
  • NOT_FOUND
  • IDEMPOTENCY_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
  • 429
  • 5xx

Logging and monitoring

Always log:

  • error.code
  • meta.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 message instead of stable code
  • retrying all 4xx responses as if they were transient
  • showing raw backend details directly to end users
  • dropping request_id from logs and then losing the easiest correlation handle
  • treating signature verification failures as ordinary license denials

Prototype docs shell for the rewrite workspace.