DOCS LLMs

Rust SDK

The official Rust SDK for LicenseSeat provides a comprehensive API for managing software licenses in native Rust applications and Tauri v2 apps. The SDK includes first-class TypeScript bindings for Tauri frontends.

Packages

This SDK provides two crates:

Crate Description Links
licenseseat Core Rust SDK for any Rust application crates.io
tauri-plugin-licenseseat Tauri v2 plugin with TypeScript bindings crates.io / npm

Installation

Tauri Apps

1. Add the Rust plugin:

cd src-tauri
cargo add tauri-plugin-licenseseat

2. Add the TypeScript bindings:

# npm
npm add @licenseseat/tauri-plugin

# pnpm
pnpm add @licenseseat/tauri-plugin

# yarn
yarn add @licenseseat/tauri-plugin

# bun
bun add @licenseseat/tauri-plugin

Pure Rust

cargo add licenseseat

For offline validation support:

cargo add licenseseat --features offline

Quick Start (Tauri)

1. Register the Plugin

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

2. Add Configuration

// tauri.conf.json
{
  "plugins": {
    "licenseseat": {
      "apiKey": "pk_live_xxxxxxxx",
      "productSlug": "your-product"
    }
  }
}

3. Add Permissions

// src-tauri/capabilities/default.json
{
  "identifier": "default",
  "windows": ["main"],
  "permissions": [
    "core:default",
    "licenseseat:default"
  ]
}

4. Use in Your Frontend

import { activate, hasEntitlement, getStatus } from '@licenseseat/tauri-plugin';

// Activate a license
const license = await activate('USER-LICENSE-KEY');
console.log(`Device ID: ${license.deviceId}`);

// Check entitlements
if (await hasEntitlement('pro-features')) {
  enableProFeatures();
}

// Get current status
const status = await getStatus();
console.log(`Status: ${status.status}`);

Quick Start (Pure Rust)

use licenseseat::{LicenseSeat, Config};

#[tokio::main]
async fn main() -> licenseseat::Result<()> {
    // 1. Initialize the SDK
    let sdk = LicenseSeat::new(Config::new("api-key", "product-slug"));

    // 2. Activate a license
    let license = sdk.activate("USER-LICENSE-KEY").await?;
    println!("Activated! Device ID: {}", license.device_id);

    // 3. Validate the license
    let result = sdk.validate().await?;
    if result.valid {
        println!("License is valid!");
    }

    // 4. Check entitlements
    if sdk.has_entitlement("pro-features") {
        println!("Pro features enabled!");
    }

    // 5. Deactivate when done
    sdk.deactivate().await?;

    Ok(())
}

Configuration

Tauri Plugin Configuration

// tauri.conf.json
{
  "plugins": {
    "licenseseat": {
      "apiKey": "pk_live_xxxxxxxx",
      "productSlug": "your-product",
      "apiBaseUrl": "https://licenseseat.com/api/v1",
      "autoValidateInterval": 3600,
      "heartbeatInterval": 300,
      "offlineFallbackMode": "network_only",
      "maxOfflineDays": 0,
      "telemetryEnabled": true,
      "debug": false
    }
  }
}

Pure 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(),
    auto_validate_interval: Duration::from_secs(3600),
    heartbeat_interval: Duration::from_secs(300),
    offline_fallback_mode: OfflineFallbackMode::NetworkOnly,
    max_offline_days: 7,
    telemetry_enabled: true,
    app_version: Some("1.0.0".into()),
    debug: false,
    ..Default::default()
};

let sdk = LicenseSeat::new(config);

Configuration Options

Option Type Default Description
apiKey / api_key String Your LicenseSeat API key (required)
productSlug / product_slug String Your product slug (required)
apiBaseUrl / api_base_url String https://licenseseat.com/api/v1 API base URL
autoValidateInterval / auto_validate_interval number / Duration 3600 (1 hour) Background validation interval
heartbeatInterval / heartbeat_interval number / Duration 300 (5 min) Heartbeat interval
offlineFallbackMode / offline_fallback_mode string / OfflineFallbackMode network_only Offline validation behavior
maxOfflineDays / max_offline_days number / u32 0 Grace period for offline mode (days)
telemetryEnabled / telemetry_enabled boolean / bool true Send device telemetry
debug boolean / bool false Enable debug logging

Offline Fallback Modes

Mode Description
network_only / NetworkOnly Always require network validation (default)
allow_offline / AllowOffline Fall back to the cached offline artifact when network is unavailable (currently a signed offline token)
offline_first / OfflineFirst Prefer offline validation, sync when online

License Lifecycle

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   Activate  │────▶│   Validate  │────▶│  Deactivate │
└─────────────┘     └─────────────┘     └─────────────┘
                          │
                          ▼
                    ┌─────────────┐
                    │  Heartbeat  │ (periodic)
                    └─────────────┘

TypeScript API

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

// Activate a license
const license = await activate('USER-LICENSE-KEY');
console.log(`Device ID: ${license.deviceId}`);
console.log(`Activation ID: ${license.activationId}`);

// Validate the current license
const result = await validate();
if (result.valid) {
  console.log(`Plan: ${result.license.planKey}`);
} else {
  console.log(`Invalid: ${result.code} - ${result.message}`);
}

// Get current status
const status = await getStatus();
switch (status.status) {
  case 'active':
    enableFeatures();
    break;
  case 'expired':
    showRenewalPrompt();
    break;
  case 'inactive':
  case 'invalid':
    showActivationPrompt();
    break;
}

// Send manual heartbeat
await heartbeat();

// Deactivate (release the seat)
await deactivate();

Rust API

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

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

// Heartbeat
let response = sdk.heartbeat().await?;
println!("Received at: {}", response.received_at);

// Deactivate
sdk.deactivate().await?;

Entitlements

Check feature access based on license entitlements.

TypeScript

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

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

// Detailed status
const status = await checkEntitlement('pro-features');
if (status.active) {
  enableProFeatures();
} else {
  switch (status.reason) {
    case 'expired':
      showRenewalPrompt();
      break;
    case 'notfound':
      showUpgradePrompt();
      break;
    case 'nolicense':
      showActivationPrompt();
      break;
  }
}

Rust

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

// Detailed status
let status = sdk.check_entitlement("pro-features");
match status.reason {
    EntitlementReason::Active => println!("Active!"),
    EntitlementReason::Expired => println!("Expired at {:?}", status.expires_at),
    EntitlementReason::NotFound => println!("Not included in plan"),
    EntitlementReason::NoLicense => println!("No active license"),
}

// List all entitlements
for entitlement in sdk.entitlements() {
    println!("{}: {:?}", entitlement.key, entitlement.expires_at);
}

React Integration

import { useState, useEffect } from 'react';
import { getStatus, hasEntitlement, activate, LicenseStatus } from '@licenseseat/tauri-plugin';

function useLicense() {
  const [status, setStatus] = useState<LicenseStatus | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    getStatus()
      .then(setStatus)
      .finally(() => setLoading(false));
  }, []);

  return { status, loading };
}

function useEntitlement(key: string) {
  const [active, setActive] = useState(false);

  useEffect(() => {
    hasEntitlement(key).then(setActive);
  }, [key]);

  return active;
}

function App() {
  const { status, loading } = useLicense();
  const hasProFeatures = useEntitlement('pro-features');

  if (loading) return <Loading />;

  if (status?.status !== 'active') {
    return <ActivationScreen />;
  }

  return (
    <div>
      <h1>Welcome!</h1>
      {hasProFeatures && <ProFeatures />}
    </div>
  );
}

Vue Integration



Svelte Integration



{#if status?.status === 'active'}
  

Welcome!

{#if hasProFeatures} {/if} {:else} {/if}

Event System

TypeScript (Tauri Events)

import { listen } from '@tauri-apps/api/event';

// Listen for license events
await listen('licenseseat://activation-success', (event) => {
  console.log('License activated!', event.payload);
});

await listen('licenseseat://validation-success', (event) => {
  console.log('License validated!', event.payload);
  refreshUI();
});

await listen('licenseseat://validation-failed', (event) => {
  console.log('Validation failed:', event.payload);
  showLicenseError();
});

await listen('licenseseat://heartbeat-success', () => {
  updateConnectionStatus(true);
});

await listen('licenseseat://heartbeat-error', () => {
  updateConnectionStatus(false);
});

Rust

use licenseseat::{LicenseSeat, EventKind};

let sdk = LicenseSeat::new(config);
let mut events = sdk.subscribe();

tokio::spawn(async move {
    while let Ok(event) = events.recv().await {
        match event.kind {
            EventKind::ActivationSuccess => println!("License activated!"),
            EventKind::ValidationSuccess => println!("Validation succeeded"),
            EventKind::ValidationFailed => println!("Validation failed"),
            EventKind::HeartbeatSuccess => println!("Heartbeat OK"),
            EventKind::HeartbeatError => println!("Heartbeat failed"),
            EventKind::DeactivationSuccess => println!("Deactivated"),
            _ => {}
        }
    }
});

Available Events

Event Description
ActivationSuccess / licenseseat://activation-success License successfully activated
ActivationError / licenseseat://activation-error Activation failed
ValidationSuccess / licenseseat://validation-success License validated successfully
ValidationFailed / licenseseat://validation-failed Validation failed
DeactivationSuccess / licenseseat://deactivation-success License deactivated
HeartbeatSuccess / licenseseat://heartbeat-success Heartbeat acknowledged
HeartbeatError / licenseseat://heartbeat-error Heartbeat failed

Offline Validation

Enable Ed25519 cryptographic offline validation for air-gapped or unreliable network environments.

Tauri Configuration

{
  "plugins": {
    "licenseseat": {
      "offlineFallbackMode": "allow_offline",
      "maxOfflineDays": 7
    }
  }
}

Rust Configuration

use licenseseat::{Config, OfflineFallbackMode};

let config = Config {
    offline_fallback_mode: OfflineFallbackMode::AllowOffline,
    max_offline_days: 7,
    ..Default::default()
};

How Offline Validation Works

Offline model note: the current Rust/Tauri SDK still uses signed offline tokens today. Machine files are the newer preferred offline artifact at the API level, but this SDK has not migrated yet.

  1. On activation, the SDK fetches a signed offline token from the server
  2. The token contains license data, entitlements, and an Ed25519 signature
  3. When offline, the SDK verifies the signature locally
  4. Clock tamper detection prevents bypassing expiration

Security Features

  • Ed25519 Signatures: Offline licenses are cryptographically signed
  • Clock Tamper Detection: Detects system clock manipulation
  • Grace Period: Configurable offline validity period
  • Secure Storage: Tokens cached locally with platform-appropriate storage

TypeScript API Reference

Functions

Function Description Returns
activate(key, options?) Activate a license key Promise<License>
validate() Validate current license Promise<ValidationResult>
deactivate() Deactivate and release seat Promise<void>
getStatus() Get current license status Promise<LicenseStatus>
hasEntitlement(key) Check if entitlement is active Promise<boolean>
checkEntitlement(key) Get detailed entitlement status Promise<EntitlementStatus>
heartbeat() Send heartbeat ping Promise<void>
getLicense() Get cached license Promise<License | null>
reset() Clear SDK state Promise<void>

Types

interface License {
  licenseKey: string;
  deviceId: string;
  activationId: string;
  activatedAt: string;
}

interface LicenseStatus {
  status: 'active' | 'inactive' | 'invalid' | 'pending' | 'offlineValid' | 'offlineInvalid';
  message?: string;
  license?: string;
  device?: string;
  activatedAt?: string;
  lastValidated?: string;
}

interface ValidationResult {
  valid: boolean;
  code?: string;
  message?: string;
  license: {
    key: string;
    status: string;
    planKey: string;
    activeEntitlements: Array<{ key: string; expiresAt?: string }>;
  };
}

interface EntitlementStatus {
  active: boolean;
  reason?: 'nolicense' | 'notfound' | 'expired';
  expiresAt?: string;
}

interface ActivationOptions {
  deviceId?: string;
  deviceName?: string;
  metadata?: Record<string, unknown>;
}

Error Handling

TypeScript

try {
  const license = await activate(key);
  showSuccess('License activated!');
} catch (error) {
  const message = error as string;

  if (message.includes('invalid')) {
    showError('Invalid license key');
  } else if (message.includes('limit')) {
    showError('Device limit reached. Deactivate another device first.');
  } else if (message.includes('expired')) {
    showError('This license has expired');
  } else if (message.includes('network')) {
    showError('Network error. Please check your connection.');
  } else {
    showError(`Activation failed: ${message}`);
  }
}

Rust

use licenseseat::Error;

match sdk.activate("KEY").await {
    Ok(license) => println!("Activated: {}", license.device_id),
    Err(Error::Api { code, message, .. }) => {
        println!("API error: {} - {}", code, message);
    }
    Err(Error::Network(e)) => {
        println!("Network error: {}", e);
    }
    Err(Error::Crypto(e)) => {
        println!("Offline validation failed: {}", e);
    }
    Err(e) => println!("Error: {}", e),
}

Common Error Codes

Code Description
license_not_found License key doesn't exist
license_expired License has expired
license_suspended License has been suspended
seat_limit_exceeded No available seats
device_mismatch Device ID doesn't match activation
product_mismatch License not valid for this product

Tauri Permissions

The plugin uses Tauri's permission system for fine-grained access control.

Permission Description
licenseseat:default All commands (recommended)
licenseseat:allow-activate Only activation
licenseseat:allow-validate Only validation
licenseseat:allow-deactivate Only deactivation
licenseseat:allow-status Only status checks
licenseseat:allow-entitlements Only entitlement checks

Telemetry

The SDK automatically collects and sends the following telemetry:

Field Description
sdk_name rust or tauri
sdk_version SDK version
os_name Operating system (macOS, Windows, Linux)
os_version OS version
platform native or tauri
device_type desktop
app_version Your app version (if configured)

See Telemetry for the full field reference.

Platform Support

Platform Minimum Version Notes
Rust 1.70+ Async runtime required (tokio)
Tauri v2.0.0+ Full plugin support
macOS 10.15+ Full support
Windows 10+ Full support
Linux glibc 2.31+ Full support

Security

API Key Protection

Your API key is stored in tauri.conf.json and compiled into your app binary. It is not exposed to the JavaScript frontend.

Device Fingerprinting

The SDK generates a stable device ID based on hardware characteristics using machine-uid. This ID is:

  • Stable across app restarts
  • Not personally identifiable
  • Used for seat tracking and offline validation

TLS

The SDK uses rustls by default for TLS, with no OpenSSL dependency.

Troubleshooting

Plugin Not Loading

  1. Ensure the plugin is registered in main.rs or lib.rs:

    .plugin(tauri_plugin_licenseseat::init())
  2. Check that permissions are added to your capability file.

  3. Rebuild the Rust backend:

    cd src-tauri && cargo build

"Command not found" Error

Make sure you've installed the JS bindings:

npm add @licenseseat/tauri-plugin

Debug Logging

Enable debug mode to see detailed SDK logs:

{
  "plugins": {
    "licenseseat": {
      "debug": true
    }
  }
}

Then check the Tauri console output for [licenseseat] prefixed messages.

Next Steps