Guides
Offline Licensing
Validate licenses without network access using Ed25519-signed license files.
Policy requirement
Offline licensing requires
offlineAllowed: true in the license policy. The desktop_app and cli_tool templates enable this by default.How It Works
- Your app calls the checkout endpoint while online with a fingerprint and TTL.
- The API returns an Ed25519-signed license certificate (base64 encoded).
- Your app stores the certificate file locally.
- While offline, the SDK verifies the Ed25519 signature and checks the expiry date.
- When the TTL expires, the user must reconnect to renew the offline license.
1. Check Out an Offline License
Checking out creates a signed license file bound to a specific device fingerprint. The TTL can range from 1 hour to 90 days (default: 14 days).
Python
checkout.py
from licentric import Licentric
client = Licentric(api_key="lk_live_your_key_here")
# Check out a license for offline use
checkout = client.licenses.checkout(
license_id="lic_abc123",
fingerprint=client.fingerprint(),
ttl=86400 * 14 # 14 days in seconds
)
# Save the signed license file locally
with open("license.lic", "w") as f:
f.write(checkout.certificate)
print(f"Offline until: {checkout.expires_at}")TypeScript
checkout.ts
import { Licentric } from "@licentric/sdk";
import { writeFileSync } from "fs";
const client = new Licentric({ apiKey: "lk_live_your_key_here" });
const checkout = await client.licenses.checkout({
licenseId: "lic_abc123",
fingerprint: client.fingerprint(),
ttl: 86400 * 14, // 14 days in seconds
});
writeFileSync("license.lic", checkout.certificate);2. Validate Offline
Offline validation verifies the Ed25519 signature (no network needed) and checks that the certificate has not expired and matches the current device fingerprint.
Python
validate_offline.py
from licentric import Licentric
client = Licentric(api_key="lk_live_your_key_here")
def validate_offline(license_file_path: str) -> bool:
"""Validate a license file without network access."""
with open(license_file_path, "r") as f:
certificate = f.read()
result = client.validate_offline(
certificate=certificate,
fingerprint=client.fingerprint()
)
if result.valid:
print(f"License valid until {result.expires_at}")
return True
if result.code == "EXPIRED":
print("Offline license expired. Connect to renew.")
else:
print(f"Offline validation failed: {result.code}")
return FalseTypeScript
validate-offline.ts
import { Licentric } from "@licentric/sdk";
import { readFileSync } from "fs";
const client = new Licentric({ apiKey: "lk_live_your_key_here" });
const certificate = readFileSync("license.lic", "utf-8");
const result = client.validateOffline({
certificate,
fingerprint: client.fingerprint(),
});
if (result.valid) {
// License is valid offline
}3. License File Anatomy
The license certificate is a signed JSON structure with three parts: header (algorithm and key ID), payload (license data), and signature.
license.lic (decoded)
{
"header": {
"alg": "Ed25519",
"kid": "key_abc123"
},
"payload": {
"licenseId": "lic_abc123",
"fingerprint": "a1b2c3d4e5f6...",
"issuedAt": "2026-01-15T00:00:00Z",
"expiresAt": "2026-01-29T00:00:00Z",
"entitlements": ["export", "premium_analytics"],
"metadata": {
"customerEmail": "user@example.com",
"productCode": "DSK"
}
},
"signature": "base64-encoded-ed25519-signature..."
}4. TTL Configuration
| Duration | Seconds | Use Case |
|---|---|---|
| 1 hour | 3,600 | Temporary offline access during commute |
| 1 day | 86,400 | Daily offline work (field teams) |
| 14 days | 1,209,600 | Default for desktop apps |
| 90 days | 7,776,000 | Air-gapped environments, on-premise |
5. Hybrid Validation
For the best user experience, try online validation first and fall back to the offline license file when the network is unavailable. Refresh the offline file whenever you successfully connect.
hybrid.py
from licentric import Licentric, ApiError
client = Licentric(api_key="lk_live_your_key_here")
def validate_hybrid(license_key: str, license_file: str) -> bool:
"""Try online validation first, fall back to offline."""
try:
# Online validation (most up-to-date)
result = client.validate(
key=license_key,
fingerprint=client.fingerprint()
)
if result.valid:
# Refresh offline license while connected
checkout = client.licenses.checkout(
license_id=result.license_id,
fingerprint=client.fingerprint(),
ttl=86400 * 14
)
save_license_file(license_file, checkout.certificate)
return result.valid
except ApiError:
# Offline — validate local license file
return validate_offline(license_file)Auto-renewal
The SDK can automatically refresh the offline license file whenever an online validation succeeds, keeping the offline TTL fresh without manual intervention.