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
Welcome!
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.
- On activation, the SDK fetches a signed offline token from the server
- The token contains license data, entitlements, and an Ed25519 signature
- When offline, the SDK verifies the signature locally
- 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
-
Ensure the plugin is registered in
main.rsorlib.rs:.plugin(tauri_plugin_licenseseat::init()) -
Check that permissions are added to your capability file.
-
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
- JavaScript SDK - For Electron and browser apps
- Swift SDK - For native macOS/iOS apps
- Offline Licensing - Air-gapped validation
- API Reference - Direct API access