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
from licentric.fingerprint import fingerprint

client = Licentric(api_key="lk_live_your_key_here")

# Check out a license for offline use (uses your API key for auth)
checkout = client.checkout(
    license_id="lic_abc123",
    fingerprint=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, fingerprint } from "@licentric/sdk";
import { writeFileSync } from "fs";

const client = new Licentric({ apiKey: "lk_live_your_key_here" });

const checkout = await client.checkout({
  licenseId: "lic_abc123",
  fingerprint: 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

# verify_offline is a static method — no API key needed, works fully offline
def validate_offline(license_file_path: str, public_key: str) -> bool:
    """Validate a license file without network access.

    public_key: your account's Ed25519 public key
                (GET /api/v1/account/signing-key)
    """
    with open(license_file_path, "r") as f:
        certificate = f.read()

    result = Licentric.verify_offline(
        license_file_data=certificate,
        public_key=public_key
    )

    if result.valid:
        print(f"License valid until {result.expires_at}")
        return True

    if result.code == "EXPIRED":
        print("Offline license expired. Connect to internet 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";

// verifyOffline is a static method — no API key needed, works fully offline
// publicKey: your account's Ed25519 public key (GET /api/v1/account/signing-key)
const certificate = readFileSync("license.lic", "utf-8");

const result = Licentric.verifyOffline(
  certificate,
  process.env.LICENTRIC_PUBLIC_KEY!
);

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": {
    "version": 1,
    "algorithm": "Ed25519"
  },
  "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
from licentric.fingerprint import fingerprint

client = Licentric(api_key="lk_live_your_key_here")
PUBLIC_KEY = "your-ed25519-public-key"  # from GET /api/v1/account/signing-key

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=fingerprint()
        )
        if result.valid:
            # Refresh offline license while connected
            checkout = client.checkout(
                license_id=result.license_id,
                fingerprint=fingerprint(),
                ttl=86400 * 14
            )
            save_license_file(license_file, checkout.certificate)
        return result.valid

    except ApiError:
        # Offline — validate local license file
        return Licentric.verify_offline(
            license_file_data=open(license_file).read(),
            public_key=PUBLIC_KEY
        ).valid
Auto-renewal
The SDK can automatically refresh the offline license file whenever an online validation succeeds, keeping the offline TTL fresh without manual intervention.