$_tuish

SDK API Reference

Complete API reference for all Tuish SDKs

SDK API Reference

Complete reference for all public types and functions in Tuish SDKs.

Main Client

Tuish

The main SDK entry point.

import { Tuish } from '@tuish/sdk';

const tuish = new Tuish(config: TuishConfig);

Methods

MethodReturnsDescription
checkLicense()Promise<LicenseCheckResult>Check license (offline-first)
purchaseInBrowser(options?)Promise<CheckoutSessionResult>Open browser checkout
waitForCheckoutComplete(sessionId, options?)Promise<LicenseCheckResult>Poll for checkout completion
purchaseInTerminal(options)Promise<PurchaseConfirmResult>Terminal purchase flow
storeLicense(key)voidSave license key manually
getCachedLicenseKey()string | nullGet cached license key
clearLicense()voidClear cached license
getMachineFingerprint()stringGet machine fingerprint
extractLicenseInfo(key)LicenseDetails | nullParse license (no verification)

SDK

The main SDK entry point.

import tuish "github.com/tuish/sdk-go"

sdk, err := tuish.New(config Config)

Methods

MethodReturnsDescription
CheckLicense(ctx)(*LicenseCheckResult, error)Check license (offline-first)
PurchaseInBrowser(ctx, email)(*CheckoutSessionResult, error)Open browser checkout
WaitForCheckoutComplete(ctx, id, interval, timeout)(*LicenseCheckResult, error)Poll for checkout completion
RequestLoginOtp(ctx, email)(*OtpRequestResult, error)Request login OTP
VerifyLogin(ctx, email, otpId, otp)(*LoginResult, error)Verify login OTP
InitTerminalPurchase(ctx)(*PurchaseInitResult, error)Initialize terminal purchase
RequestPurchaseOtp(ctx)(otpId string, expiresIn int, error)Request purchase OTP
ConfirmTerminalPurchase(ctx, cardId, otpId, otp)(*PurchaseConfirmResult, error)Confirm purchase
StoreLicense(key)errorSave license key manually
GetCachedLicenseKey()stringGet cached license key
ClearLicense()errorClear cached license
GetMachineFingerprint()stringGet machine fingerprint
ExtractLicenseInfo(key)(*LicenseDetails, error)Parse license (no verification)

Tuish

The main SDK entry point.

use tuish::Tuish;

let tuish = Tuish::builder()
    .product_id("prod_xxx")
    .public_key("MCowBQYDK2VwAyEA...")
    .build()?;

Methods

MethodReturnsDescription
check_license()LicenseCheckResultCheck license (sync, offline-first)
check_license_async()Result<LicenseCheckResult>Check license (async with refresh)
validate_online()Result<LicenseCheckResult>Force online validation
open_checkout(email)Result<CheckoutSession>Open browser checkout
purchase_in_browser(email)Result<CheckoutSession>Get checkout URL
wait_for_checkout(id)Result<LicenseCheckResult>Poll for checkout
purchase_in_terminal(...)Result<LicenseCheckResult>Terminal purchase flow
save_license(key)Result<LicenseCheckResult>Save license key
clear_license()Result<()>Clear cached license
config()&TuishConfigGet current config
product_id()&strGet product ID

Builder

Tuish::builder()
    .product_id(id: &str)
    .public_key(key: &str)
    .api_key(key: &str)        // Optional
    .api_url(url: &str)        // Optional
    .build() -> Result<Tuish>

Tuish

The main SDK entry point.

from tuish import Tuish

client = Tuish(
    product_id="prod_xxx",
    public_key="MCowBQYDK2VwAyEA...",
)

Methods

MethodReturnsDescription
check_license()LicenseCheckResultCheck license (offline-first)
purchase_in_browser(email=None)CheckoutSessionOpen browser checkout
wait_for_checkout_complete(session_id)LicenseCheckResultPoll for checkout
get_machine_fingerprint()strGet machine fingerprint

Configuration

interface TuishConfig {
  productId: string;           // Required
  publicKey: string;           // Required - Ed25519 public key
  apiBaseUrl?: string;         // Default: https://api.tuish.dev
  apiKey?: string;             // For authenticated requests
  storageDir?: string;         // Default: ~/.tuish/licenses/
  debug?: boolean;             // Enable debug logging
}
type Config struct {
    ProductID  string  // Required
    PublicKey  string  // Required - Ed25519 (SPKI base64 or hex)
    APIBaseURL string  // Default: https://api.tuish.dev
    APIKey     string  // For authenticated requests
    StorageDir string  // Default: ~/.tuish/licenses/
    Debug      bool    // Enable debug logging
}
pub struct TuishConfig {
    pub product_id: String,
    pub public_key: String,
    pub api_url: String,
    pub api_key: Option<String>,
}

impl TuishConfig {
    pub fn new(product_id: &str, public_key: &str) -> Self;
}
Tuish(
    product_id: str,           # Required
    public_key: str,           # Required - Ed25519 public key
    api_base_url: str = "https://api.tuish.dev",
    api_key: str | None = None,
    storage_dir: str = "~/.tuish/licenses",
    debug: bool = False,
)

License Types

LicenseCheckResult

interface LicenseCheckResult {
  valid: boolean;
  license?: LicenseDetails;
  reason?: LicenseInvalidReason;
  offlineVerified: boolean;
  machineBound?: boolean;         // Whether license is bound to a machine
  machineFingerprint?: string;    // Fingerprint the license is bound to
}

LicenseDetails

interface LicenseDetails {
  id: string;
  productId: string;
  productName?: string;
  features: string[];
  status: 'active' | 'expired' | 'revoked';
  issuedAt: number;      // Unix timestamp (ms)
  expiresAt: number | null;  // null = perpetual
}

LicenseInvalidReason

type LicenseInvalidReason =
  | 'not_found'
  | 'expired'
  | 'revoked'
  | 'invalid_format'
  | 'invalid_signature'
  | 'machine_mismatch'
  | 'network_error';

LicenseCheckResult

type LicenseCheckResult struct {
    Valid           bool
    License         *LicenseDetails
    Reason          LicenseInvalidReason
    OfflineVerified bool
}

LicenseDetails

type LicenseDetails struct {
    ID          string
    ProductID   string
    ProductName string
    Features    []string
    Status      LicenseStatus
    IssuedAt    int64   // Unix timestamp (ms)
    ExpiresAt   *int64  // nil = perpetual
}

LicenseInvalidReason

const (
    ReasonNotFound         = "not_found"
    ReasonExpired          = "expired"
    ReasonRevoked          = "revoked"
    ReasonInvalidFormat    = "invalid_format"
    ReasonInvalidSignature = "invalid_signature"
    ReasonMachineMismatch  = "machine_mismatch"
    ReasonNetworkError     = "network_error"
)

LicenseCheckResult

pub struct LicenseCheckResult {
    pub valid: bool,
    pub license: Option<LicenseDetails>,
    pub reason: Option<LicenseInvalidReason>,
    pub offline_verified: bool,
}

LicenseDetails

pub struct LicenseDetails {
    pub id: String,
    pub product_id: String,
    pub product_name: Option<String>,
    pub features: Vec<String>,
    pub status: LicenseStatus,
    pub issued_at: i64,
    pub expires_at: Option<i64>,
}

LicenseInvalidReason

pub enum LicenseInvalidReason {
    NotFound,
    Expired,
    Revoked,
    InvalidFormat,
    InvalidSignature,
    WrongMachine,
    NetworkError,
}

LicensePayload

Raw license token payload:

pub struct LicensePayload {
    pub lid: String,           // License ID
    pub pid: String,           // Product ID
    pub cid: String,           // Customer ID
    pub did: String,           // Developer ID
    pub features: Vec<String>,
    pub iat: i64,              // Issued at (ms)
    pub exp: Option<i64>,      // Expires at (ms)
    pub mid: Option<String>,   // Machine ID
}

impl LicensePayload {
    pub fn is_expired(&self) -> bool;
    pub fn has_feature(&self, feature: &str) -> bool;
}

Checkout Types

interface CheckoutSession {
  sessionId: string;
  checkoutUrl: string;
}

interface CheckoutOptions {
  email?: string;
  openBrowser?: boolean;
}

interface WaitOptions {
  pollIntervalMs?: number;  // Default: 2000
  timeoutMs?: number;       // Default: 600000
  onPoll?: (status: 'pending' | 'complete' | 'expired') => void;
}
type CheckoutSession struct {
    SessionID   string
    CheckoutURL string
}
pub struct CheckoutSession {
    pub session_id: String,
    pub checkout_url: String,
}

Error Types

The SDK has a two-tier error system:

  1. API Errors - Thrown when HTTP requests to the API fail
  2. License Verification Reasons - Returned in LicenseCheckResult.reason (not thrown)

TuishApiError

Thrown when API requests fail:

class TuishApiError extends Error {
  statusCode: number;
  code: ErrorCode;
  details?: Record<string, unknown>;
}

type ErrorCode =
  | 'not_found'
  | 'invalid_request'
  | 'unauthorized'
  | 'forbidden'
  | 'conflict'
  | 'internal_error';

License Verification Reasons

Returned in LicenseCheckResult.reason (see LicenseInvalidReason above):

// Example handling
const result = await tuish.checkLicense();
if (!result.valid) {
  switch (result.reason) {
    case 'expired':
      console.log('License has expired');
      break;
    case 'machine_mismatch':
      console.log('License is bound to a different machine');
      break;
    // ...
  }
}
#[derive(Debug, thiserror::Error)]
pub enum TuishError {
    #[error("Invalid license: {0}")]
    InvalidLicense(String),

    #[error("License has expired")]
    ExpiredLicense,

    #[error("Invalid signature")]
    InvalidSignature,

    #[error("License is for a different machine")]
    InvalidMachineId,

    #[error("Network error: {0}")]
    NetworkError(String),

    #[error("Storage error: {0}")]
    StorageError(String),

    #[error("API error ({status}): {message}")]
    ApiError { status: u16, message: String },

    #[error("Parse error: {0}")]
    ParseError(String),

    #[error("Invalid public key: {0}")]
    InvalidPublicKey(String),

    #[error("Feature not available: {0}")]
    FeatureNotAvailable(String),
}

Crypto Functions (Rust)

/// Verify a license key cryptographically
pub fn verify_license(
    license_key: &str,
    public_key: &str,
    machine_id: Option<&str>,
) -> Result<LicensePayload, TuishError>;

/// Parse a license without verification
pub fn parse_license(
    license_key: &str,
) -> Result<(LicenseHeader, LicensePayload, Vec<u8>), TuishError>;

/// Check if a license payload is expired
pub fn is_license_expired(payload: &LicensePayload) -> bool;

/// Get remaining time on a license
pub fn get_license_time_remaining(payload: &LicensePayload) -> Option<Duration>;

/// Generate machine fingerprint
pub fn get_machine_fingerprint() -> String;

/// Get cached machine fingerprint
pub fn get_machine_fingerprint_cached() -> &'static str;

Constants

/// Default API base URL
pub const DEFAULT_API_URL: &str = "https://api.tuish.dev";

/// Cache refresh interval (24 hours)
pub const CACHE_REFRESH_INTERVAL_MS: i64 = 24 * 60 * 60 * 1000;

Feature Flags (Rust)

FeatureDefaultDescription
httpYesOnline validation, purchases
storageYesLocal license caching
browserYesBrowser checkout flow
# Minimal (offline-only)
tuish = { version = "0.1", default-features = false }

# With specific features
tuish = { version = "0.1", default-features = false, features = ["storage"] }