DOCS LLMs

Rust SDK

Official Rust SDK and Tauri plugin for LicenseSeat. Use the core licenseseat crate in native Rust applications, or pair tauri-plugin-licenseseat with @licenseseat/tauri-plugin in Tauri v2 apps.

Current stable release: v0.6.0. The matching core crate, Tauri crate, and frontend bindings are all version 0.6.0.

Packages

This SDK family ships as three published packages:

Package Description Links
licenseseat Core Rust SDK for native Rust apps crates.io 0.6.0
tauri-plugin-licenseseat Rust-side Tauri v2 plugin crates.io 0.6.0
@licenseseat/tauri-plugin TypeScript/JavaScript bindings for the Tauri plugin npm 0.6.0

Features

  • Full license lifecycle: activate, validate, deactivate, heartbeat
  • Machine-file-first offline validation with Ed25519 signatures and AES-256-GCM payload encryption
  • Durable random installation identity by default, with opt-in C++-compatible hardware fingerprint components
  • Entitlement checks and cached validation state
  • Background re-validation, heartbeat, and network recheck loops
  • Restore cached license state on app startup
  • Release discovery and signed download-token APIs
  • Tauri v2 plugin with typed JS/TS bindings, state snapshots, and event subscriptions
  • Manual offline helpers for legacy offline tokens and machine files

Installation

Pure Rust

cargo add [email protected]

Offline validation support is included in the default feature set. If you disable default features and still want offline verification, add it back explicitly:

cargo add [email protected] --no-default-features --features "native-tls,offline"

Tauri v2

Install the Rust plugin in src-tauri/:

cd src-tauri
cargo add [email protected]

Install the frontend bindings in your app:

npm add @licenseseat/[email protected]

For applications, review and commit Cargo.lock and the frontend package-manager lockfile so the complete dependency graph—not only these direct requirements—is reproducible in production.

Quick Start

Pure Rust

use licenseseat::{Config, LicenseSeat};

#[tokio::main]
async fn main() -> licenseseat::Result<()> {
    let sdk = LicenseSeat::try_new(Config::new("pk_live_xxxxxxxx", "your-product"))?;

    sdk.activate("USER-LICENSE-KEY").await?;
    println!("License activated");

    let validation = sdk.validate().await?;
    if validation.valid {
        println!("Plan: {}", validation.license.plan_key);
    }

    if sdk.has_entitlement("pro-features") {
        enable_pro_features();
    }

    Ok(())
}

Tauri v2

Register the plugin:

// src-tauri/src/main.rs
fn main() {
    tauri::Builder::default()
        .plugin(tauri_plugin_licenseseat::init())
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

Configure it in tauri.conf.json:

{
  "plugins": {
    "licenseseat": {
      "apiKey": "pk_live_xxxxxxxx",
      "productSlug": "your-product"
    }
  }
}

Add permissions:

{
  "identifier": "default",
  "windows": ["main"],
  "permissions": [
    "core:default",
    "licenseseat:default"
  ]
}

Use it in the frontend:

import {
  activate,
  bootstrapState,
  hasAnyEntitlement,
  subscribeState,
} from '@licenseseat/tauri-plugin';

const state = await bootstrapState();
if (!state.license) {
  await activate('USER-LICENSE-KEY');
}

if (await hasAnyEntitlement(['pro-features', 'cloud-sync'])) {
  enableProFeatures();
}

const unlisten = await subscribeState(({ state: nextState }) => {
  console.log('Client status:', nextState.clientStatus);
}, { emitCurrent: true });

Configuration

Rust Configuration

use licenseseat::{Config, OfflineFallbackMode};
use std::time::Duration;

let config = Config {
    api_key: "pk_live_xxxxxxxx".into(),
    product_slug: "your-product".into(),
    api_base_url: "https://licenseseat.com/api/v1".into(),
    storage_prefix: "licenseseat_".into(),
    storage_path: None,
    device_identifier: None,
    signing_public_key: None,
    signing_key_id: None,
    auto_validate_interval: Duration::from_secs(3600),
    heartbeat_interval: Duration::from_secs(300),
    network_recheck_interval: Duration::from_secs(30),
    request_timeout: Duration::from_secs(30),
    verify_ssl: true,
    max_retries: 3,
    retry_delay: Duration::from_secs(1),
    offline_fallback_mode: OfflineFallbackMode::NetworkOnly,
    offline_token_refresh_interval: Duration::from_secs(259_200),
    enable_legacy_offline_tokens: false,
    max_offline_days: 0,
    max_clock_skew: Duration::from_secs(300),
    telemetry_enabled: true,
    app_version: Some("1.0.0".into()),
    app_build: Some("100".into()),
    debug: false,
    ..Default::default()
};

Tauri Plugin Configuration

{
  "plugins": {
    "licenseseat": {
      "apiKey": "pk_live_xxxxxxxx",
      "productSlug": "your-product",
      "apiBaseUrl": "https://licenseseat.com/api/v1",
      "storagePrefix": "licenseseat_",
      "storagePath": null,
      "deviceIdentifier": null,
      "signingPublicKey": "BASE64_ED25519_PUBLIC_KEY",
      "signingKeyId": "production-key-v1",
      "autoValidateInterval": 3600,
      "heartbeatInterval": 300,
      "networkRecheckInterval": 30,
      "timeoutSeconds": 30,
      "verifySsl": true,
      "offlineFallbackMode": "networkOnly",
      "offlineTokenRefreshInterval": 259200,
      "enableLegacyOfflineTokens": false,
      "maxOfflineDays": 7,
      "telemetryEnabled": true,
      "appVersion": "1.0.0",
      "appBuild": "100",
      "debug": false
    }
  }
}

Key Options

Rust Tauri Description
api_key apiKey Publishable API key
product_slug productSlug Product slug
api_base_url apiBaseUrl API base URL
storage_prefix storagePrefix Cache namespace prefix
storage_path storagePath Optional persisted cache directory
device_identifier deviceIdentifier Override the default durable random installation identity
send_fingerprint_components sendFingerprintComponents Opt in to raw hardware fingerprint component collection (default false)
signing_public_key signingPublicKey Optional pinned Ed25519 key for offline verification
signing_key_id signingKeyId Key ID associated with signing_public_key
auto_validate_interval autoValidateInterval Background validation interval
heartbeat_interval heartbeatInterval Background heartbeat interval
network_recheck_interval networkRecheckInterval Connectivity recheck interval while offline
request_timeout timeoutSeconds HTTP request timeout
max_retries maxRetries Maximum retry attempts for retryable requests (default 3)
retry_delay_seconds retryDelaySeconds Initial retry delay in seconds; exponential backoff is capped by the SDK (default 1)
verify_ssl verifySsl TLS certificate verification
offline_fallback_mode offlineFallbackMode Offline fallback strategy
offline_token_refresh_interval offlineTokenRefreshInterval Periodic refresh interval for cached offline artifacts. Zero disables periodic refresh, not the one-shot sync after activation.
enable_legacy_offline_tokens enableLegacyOfflineTokens Allow legacy offline-token fallback
max_clock_skew_seconds maxClockSkewSeconds Maximum accepted clock skew for signed offline artifacts, in seconds (default 300)
max_offline_days maxOfflineDays Optional host-side signed-artifact age cap. 0 means no additional cap; signed expiry is still enforced.
telemetry_enabled telemetryEnabled Telemetry toggle
app_version appVersion App version for telemetry
app_build appBuild App build for telemetry
debug debug Debug logging
emit_frontend_events emitFrontendEvents Emit SDK events to the webview (default true). Set false when the app exposes a narrow native licensing facade; native Rust subscribers still receive events

Offline Validation

The Rust SDK now follows the same machine-file-first offline flow as the reference C++ SDK.

Fallback Modes

Rust Tauri Behavior
OfflineFallbackMode::NetworkOnly omit the option or use networkOnly Default. May consult a signed offline artifact after a transport failure, timeout, HTTP 408, or 5xx response.
OfflineFallbackMode::Always always Includes every NetworkOnly case and HTTP 429 rate limiting—nothing else.

Neither mode overrides authentication or configuration failures, ordinary client/business errors, malformed or identity-substituted responses, or superseded local operations. networkOnly is the canonical default; network_only is also accepted. The legacy aliases allow_offline, allowOffline, offline_first, and offlineFirst remain accepted only for compatibility and map to always.

Production offline startup requires both a pinned signing_public_key / signingPublicKey and its matching signing_key_id / signingKeyId. A key fetched online may establish trust for the current process; its persisted copy is diagnostic cache and is deliberately not a trust anchor after restart.

How It Works

  1. Activation binds the license to a canonical fingerprint.
  2. The SDK starts one background machine-file checkout after activation.
  3. The machine file is Ed25519-signed and AES-256-GCM encrypted.
  4. When offline, the SDK verifies the signature, decrypts the payload, checks the fingerprint binding, and enforces expiry/grace rules locally.
  5. Legacy offline tokens remain available only as an explicit compatibility fallback via enable_legacy_offline_tokens.

Activation already schedules the initial offline-asset sync. Do not normally call sync_offline_assets() / syncOfflineAssets() immediately afterward: that starts a second, serialized checkout. Use the manual method later when an explicit refresh or retry is intentional. Applications that need to confirm initial readiness should subscribe to the machine-file/offline-asset events before activation.

Manual Offline Helpers

Pure Rust:

let machine_file = sdk.checkout_machine_file("USER-LICENSE-KEY", None, Some(30)).await?;
let verification = sdk.verify_machine_file(&machine_file, None, None, None)?;

if verification.valid {
    println!("Offline machine file verified");
}

Tauri frontend:

import {
  checkoutMachineFile,
  verifyMachineFile,
  fetchSigningKey,
} from '@licenseseat/tauri-plugin';

const machineFile = await checkoutMachineFile('USER-LICENSE-KEY');
await fetchSigningKey('offline-key-1');
const result = await verifyMachineFile(machineFile);

if (result.valid) {
  enableOfflineMode();
}

Legacy offline-token helpers remain available for manual workflows:

  • Rust: generate_offline_token(), verify_offline_token()
  • Tauri: generateOfflineToken(), verifyOfflineToken()

For a later intentional refresh or retry while the app is online, the Tauri plugin exposes syncOfflineAssets(). The initial post-activation sync is automatic, so an immediate manual call is redundant.

License Lifecycle

Rust

let license = sdk.activate("USER-LICENSE-KEY").await?;

let validation = sdk.validate().await?;
if validation.valid {
    println!("License valid");
}

let heartbeat = sdk.heartbeat().await?;
println!("Heartbeat acknowledged at {}", heartbeat.received_at);

sdk.deactivate().await?;

Tauri

import {
  activate,
  validate,
  heartbeat,
  deactivate,
  getClientStatus,
} from '@licenseseat/tauri-plugin';

await activate('USER-LICENSE-KEY');

const validation = await validate();
if (validation.valid) {
  console.log('License valid');
}

await heartbeat();
console.log(await getClientStatus()); // active, offline_valid, inactive, invalid, pending, offline_invalid

await deactivate();

For most Tauri apps, treat the plugin as a stateful client rather than wiring every lifecycle event manually:

  • Use bootstrapState() on startup to restore cached state and optionally run one validation pass.
  • Use activateAndGetState() after a successful activation flow when your UI needs the refreshed state immediately.
  • Use getState() for one-off reads.
  • Use subscribeState() for UI updates driven by lifecycle changes.
  • Use normalizeError() before rendering frontend-facing errors.
import {
  activateAndGetState,
  bootstrapState,
  normalizeError,
  subscribeState,
} from '@licenseseat/tauri-plugin';

const initialState = await bootstrapState();

const unlisten = await subscribeState(({ state }) => {
  renderLicenseState(state);
}, { emitCurrent: true });

try {
  const nextState = await activateAndGetState('USER-LICENSE-KEY');
  renderLicenseState(nextState);
} catch (error) {
  const licenseError = normalizeError(error);
  showError(licenseError.message);
}

Restore, Releases, and Distribution

The Rust and Tauri SDKs now expose the same restore and release-management APIs as the C++ SDK.

Restore Cached State

Rust:

let restored = sdk.restore_license().await;
if restored.restored {
    println!("Restored cached session");
}

Tauri:

import { restoreLicense } from '@licenseseat/tauri-plugin';

const restored = await restoreLicense();
if (restored.restored) {
  console.log(restored.status.status);
}

Releases and Download Tokens

Rust:

let release = sdk.get_latest_release(None, None, None).await?;
let token = sdk
    .generate_download_token(&release.version, "USER-LICENSE-KEY", None, None)
    .await?;

Tauri:

import { getLatestRelease, generateDownloadToken } from '@licenseseat/tauri-plugin';

const release = await getLatestRelease();
const token = await generateDownloadToken(release.version, 'USER-LICENSE-KEY');

Tauri API Surface

Core Commands

Function Description
activate(key, options?) Activate a license key
validateKey(key) Validate an explicit license key
validate() Validate the current cached license
deactivate() Deactivate the current license
deactivateKey(key, fingerprint?) Deactivate an explicit license/fingerprint pair
heartbeat() Send a heartbeat for the current license
heartbeatKey(key, fingerprint?) Heartbeat an explicit license/fingerprint pair
getStatus() Get the structured status object
getClientStatus() Get the stable status string
isOnline() Check the SDK's current online/offline view
getFingerprint() Read the current fingerprint
restoreLicense() Restore cached license state
health() Check API reachability
reset() Clear cached SDK state

State, Events, and UI Helpers

Function Description
getState() Get a consolidated SDK state snapshot
getAdminSnapshot() Get a detailed admin/debug snapshot
restoreAndGetState() Restore a cached session and return the refreshed state
activateAndGetState(key, options?) Activate, attempt validation, and return the latest state
bootstrapState(options?) Restore, optionally validate, and return the latest state
listenEvent(name, handler) Subscribe to a specific stable plugin event
subscribeState(listener, options?) Subscribe to state-changing lifecycle events
normalizeError(error) Normalize invoke/plugin errors into LicenseSeatPluginError

Entitlements and Cached State

Function Description
checkEntitlement(key) Get detailed entitlement status
hasEntitlement(key) Boolean entitlement check
hasAnyEntitlement(keys) True when any provided entitlement is active
hasAllEntitlements(keys) True when all provided entitlements are active
getEntitlements() List active entitlements from cached validation
getLicense() Get the cached license, if any
getActiveEntitlementKeys() List active entitlement keys from the current state snapshot
getPlanKey() Get the current plan key from the state snapshot
getLicenseMode() Get the current license mode from the state snapshot

Releases and Distribution

Function Description
getLatestRelease(productSlug?, channel?, platform?) Fetch the latest published release
listReleases(productSlug?, options?) List releases with pagination metadata
generateDownloadToken(version, licenseKey, productSlug?, platform?) Create a signed release download token

Offline Helpers

Function Description
generateOfflineToken(key, fingerprint?, ttlDays?) Generate a legacy offline token
verifyOfflineToken(token, publicKeyB64?) Verify a legacy offline token locally
checkoutMachineFile(key, options?) Checkout a machine file
fetchSigningKey(keyId) Fetch and cache an Ed25519 public key
syncOfflineAssets() Refresh cached machine-file/signing-key/token artifacts
verifyMachineFile(file, options?) Verify and decrypt a machine file locally

Entitlements

Rust:

if sdk.has_entitlement("cloud-sync") {
    enable_cloud_sync();
}

let status = sdk.check_entitlement("pro-features");
if status.active {
    enable_pro_features();
}

Tauri:

import { hasEntitlement, checkEntitlement, getEntitlements } from '@licenseseat/tauri-plugin';

if (await hasEntitlement('cloud-sync')) {
  enableCloudSync();
}

const status = await checkEntitlement('pro-features');
const entitlements = await getEntitlements();

Events

Both the Rust crate and the Tauri plugin expose license lifecycle events.

Rust

let mut events = sdk.subscribe();

tokio::spawn(async move {
    while let Ok(event) = events.recv().await {
        println!("event = {}", event.kind);
    }
});

Tauri

import {
  LICENSESEAT_EVENTS,
  listenEvent,
  subscribeState,
} from '@licenseseat/tauri-plugin';

await listenEvent(LICENSESEAT_EVENTS.VALIDATION_SUCCESS, () => {
  console.log('License validation succeeded');
});

const unlisten = await subscribeState(({ state, eventName }) => {
  console.log('State changed via', eventName, state.clientStatus);
}, { emitCurrent: true });

Common event families:

  • Activation, validation, deactivation, and heartbeat success/error events
  • Online/offline network state events
  • Offline validation success/failure events
  • Machine-file fetch, ready, verified, and verification-failed events
  • Legacy offline-token events for compatibility flows

Status Values

The stable client-status values are:

  • active
  • offline_valid
  • offline_invalid
  • inactive
  • invalid
  • pending

Tauri's getClientStatus() and getStatus() use these snake_case values.

Tauri Permissions

The plugin integrates with Tauri's permissions system. licenseseat:default is intentionally limited to ordinary lifecycle, status, and entitlement operations; it does not grant every diagnostic, offline-management, release, or destructive command.

Permission set Additional surface
licenseseat:diagnostics Detailed admin snapshots, including cached-artifact and local-path diagnostics
licenseseat:advanced-lifecycle Explicit key/fingerprint operations and destructive reset
licenseseat:offline-management Raw artifact checkout/verification, signing-key lookup, and refresh
licenseseat:releases Release lookup and license-bound download-token generation

For fine-grained access, use the generated per-command permission identifiers, for example:

  • licenseseat:allow-get-state
  • licenseseat:allow-get-admin-snapshot
  • licenseseat:allow-sync-offline-assets
  • licenseseat:allow-verify-machine-file

These command-specific identifiers are generated from the plugin command surface and shipped with the crate under permissions/autogenerated/commands/.

Do not load untrusted remote content into a Tauri window that has LicenseSeat permissions. The generic renderer API necessarily handles customer-entered license keys and returns licensing state. Higher-assurance applications should keep those permissions out of the renderer, manage the core SDK in Rust, and expose a narrow app-specific command facade with redacted state and native entitlement gates.

Platform Requirements

Platform Requirement
Rust 1.85+ for the core crate; 1.88+ for the Tauri plugin
Tauri v2.0.0+
Node.js 18+ for the JS bindings
TLS rustls by default, native-tls optional in the core crate

Security Notes

  • Use only a restricted pk_* publishable key in client applications. Never ship an sk_*, administrator, or server-write credential, and assume every embedded client key can be extracted.
  • Keep license keys out of URLs, logs, crash reports, analytics, renderer events, and untrusted IPC responses.
  • Machine files are signed, encrypted, activation-bound, and fingerprint-bound.
  • The SDK caches signing keys by kid and can also use a pinned public key.
  • As of 0.6.0, the default installation identity is a durable random UUID, scoped to the app/product and persisted in application data; existing legacy identifiers are adopted where safe. It is stable, used for seat binding and offline validation, and is not derived from hardware.
  • Raw hardware fingerprint components (platform machine identifiers and a hostname fallback) are collected and sent only with the explicit send_fingerprint_components = true opt-in, or when a caller supplies a component map for a single machine-file checkout. Hardware-derived identity remains available through these opt-ins for legacy interoperability. Treat any collected components as device data rather than anonymous data in the host application's privacy disclosures.