Guides
Desktop App Licensing
Lock your desktop application to specific devices and validate licenses on startup.
desktop_app policy template, which sets: 2 devices, offline 14 days, fingerprint required.1. Create a Product
A product represents your desktop application. The code becomes the license key prefix (e.g., DSK-XXXX-XXXX-XXXX-XXXX).
Python
from licentric import Licentric
client = Licentric(api_key="lk_live_your_key_here")
product = client.products.create(
name="My Desktop App",
code="DSK"
)TypeScript
import { Licentric } from "@licentric/sdk";
const client = new Licentric({ apiKey: "lk_live_your_key_here" });
const product = await client.products.create({
name: "My Desktop App",
code: "DSK",
});2. Create a Policy
Use the desktop_app template to get sensible defaults for device-locked desktop software.
Python
policy = client.policies.create(
name="Desktop License",
product_id=product.id,
template="desktop_app" # 2 devices, offline 14 days, fingerprint required
)TypeScript
const policy = await client.policies.create({
name: "Desktop License",
productId: product.id,
template: "desktop_app", // 2 devices, offline 14 days, fingerprint required
});3. Issue License Keys
Issue a license key to each customer. The key uses the product code as its prefix and is auto-generated in the format PREFIX-XXXX-XXXX-XXXX-XXXX.
license = client.licenses.create(
policy_id=policy.id,
email="customer@example.com"
)
# Give this key to your customer:
# DSK-XXXX-XXXX-XXXX-XXXX
print(f"License key: {license.key}")4. Validate on Launch
Validate the license key when your application starts. If the device has not been activated yet, the SDK handles activation automatically.
Python
import sys
from licentric import Licentric, ValidationError
client = Licentric(api_key="lk_live_your_key_here")
def validate_license(license_key: str) -> bool:
"""Validate license on application launch."""
try:
result = client.validate(
key=license_key,
fingerprint=client.fingerprint()
)
if result.valid:
return True
if result.code == "FINGERPRINT_NOT_FOUND":
# First launch on this device — activate it
client.machines.activate(
license_id=result.license_id,
fingerprint=client.fingerprint()
)
return True
print(f"License invalid: {result.code}")
return False
except ValidationError as e:
print(f"Validation error: {e.message}")
return False
# On app startup
LICENSE_KEY = load_key_from_config()
if not validate_license(LICENSE_KEY):
sys.exit(1)TypeScript
import { Licentric } from "@licentric/sdk";
const client = new Licentric({ apiKey: "lk_live_your_key_here" });
async function validateLicense(licenseKey: string): Promise<boolean> {
const result = await client.validate({
key: licenseKey,
fingerprint: client.fingerprint(),
});
if (result.valid) {
return true;
}
if (result.code === "FINGERPRINT_NOT_FOUND") {
// First launch on this device — activate it
await client.machines.activate({
licenseId: result.licenseId,
fingerprint: client.fingerprint(),
});
return true;
}
throw new Error(`License invalid: ${result.code}`);
}5. Re-validate Periodically
Desktop apps should re-validate in the background to detect revocations, suspensions, and expiration. A 24-hour interval works well for most applications.
import threading
def start_periodic_validation(license_key: str, interval_hours: int = 24):
"""Re-validate license in the background every N hours."""
def _revalidate():
result = client.validate(
key=license_key,
fingerprint=client.fingerprint()
)
if not result.valid:
notify_user("License is no longer valid. Please renew.")
# Schedule next check
timer = threading.Timer(interval_hours * 3600, _revalidate)
timer.daemon = True
timer.start()
_revalidate()6. Handle Offline Mode
The desktop_app template allows up to 14 days of offline use. Cache the validation result locally so the app works without a network connection.
from licentric import Licentric, ApiError
def validate_with_offline_fallback(license_key: str) -> bool:
"""Validate online, fall back to cached result if offline."""
try:
result = client.validate(
key=license_key,
fingerprint=client.fingerprint()
)
# Cache the result locally for offline use
save_to_cache(result)
return result.valid
except ApiError:
# Network unreachable — check local cache
cached = load_from_cache()
if cached and not cached.expired:
return cached.valid
return False