Skip to main content

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

  1. Your app calls the checkout endpoint while online with a fingerprint and TTL.
  2. The API returns an Ed25519-signed license certificate (base64 encoded).
  3. Your app stores the certificate file locally.
  4. While offline, the SDK verifies the Ed25519 signature and checks the expiry date.
  5. 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 False

TypeScript

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

DurationSecondsUse Case
1 hour3,600Temporary offline access during commute
1 day86,400Daily offline work (field teams)
14 days1,209,600Default for desktop apps
90 days7,776,000Air-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.