ExisOne Python SDK

Embed our cross-platform Python library to generate hardware IDs, activate and validate licenses, and send support tickets.

Package: exisone-client · Version: 0.7.0 · Python: 3.8+
View on PyPI →

Install

pip install exisone-client

Initialize

from exisone import ExisOneClient, ExisOneClientOptions

options = ExisOneClientOptions(
    base_url="https://your-api-host",  # must be https
    access_token="exo_at_<public>_<secret>",  # create in Access Tokens UI
    offline_public_key=None  # optional: set for offline license validation
)

client = ExisOneClient(options)

# Optional: change base URL later
client.with_base_url("https://another-host")

# Library version
sdk_version = client.get_version()

base_url can also be set via environment variable EXISONE_BASEURL.

For offline license validation, set offline_public_key to your tenant's RSA public key (PEM format). Obtain this from the Crypto Keys page.

Capabilities

Quick Start

from exisone import ExisOneClient, ExisOneClientOptions

# Initialize
options = ExisOneClientOptions(
    base_url="https://www.exisone.com",
    access_token="exo_at_xxx_yyy"
)
client = ExisOneClient(options)

# 1) Hardware ID (store locally)
hwid = client.generate_hardware_id()

# 2) Activation with version (user enters key/email)
result = client.activate(
    activation_key=activation_key,
    email=user_email,
    hardware_id=hwid,
    product_name="MyProduct",
    version="1.0.0"
)
if not result.success:
    if result.error_code == "version_outdated":
        print(f"Please upgrade to version {result.minimum_required_version}")
    else:
        print(result.error_message)

# 3) Validate on app start (requires 'verify' permission)
result = client.validate(
    activation_key=activation_key,
    hardware_id=hwid,
    product_name="MyProduct",
    version="1.0.0"
)

if result.status == "version_outdated":
    print(f"Update required: minimum version is {result.minimum_required_version}")
elif result.is_valid:
    print(f"Licensed until: {result.expiration_date}")
    print(f"Features: {', '.join(result.features)}")

# 4) Optional: Deactivate from the same hardware
success = client.deactivate(activation_key, hwid, "MyProduct")

# 5) (Publisher tooling) Generate a new key for a product
key = client.generate_activation_key("MyProduct", "user@example.com", plan_id=1)

# 6) Submit support ticket from client
client.send_support_ticket("MyProduct", user_email, "Subject", "Message body")

Offline License Validation

For customers without internet access, generate offline activation codes from the License Keys page by providing their Hardware ID. These codes are RSA-signed and validated locally.

Setup

# Configure with your tenant's RSA public key for offline validation
options = ExisOneClientOptions(
    base_url="https://your-api-host",
    access_token="your-token",
    offline_public_key="""-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...
-----END PUBLIC KEY-----"""
)
client = ExisOneClient(options)

Smart Validation (Recommended)

Auto-detects online vs offline keys based on format. Falls back to offline validation if server is unreachable.

hwid = client.generate_hardware_id()

# Works with both online keys (XXXX-XXXX-XXXX-XXXX) and offline codes
result = client.validate_smart(license_key_or_offline_code, hwid, "MyProduct")

if result.is_valid:
    print(f"Licensed until: {result.expiration_date}")
    print(f"Offline mode: {result.was_offline}")
    print(f"Features: {', '.join(result.features)}")
else:
    print(f"Invalid: {result.error_message}")

Direct Offline Validation (No Network)

# Validate completely offline - no server calls
result = client.validate_offline(offline_code, hwid)

if result.is_valid:
    print(f"Product: {result.product_name}")
    print(f"Expires: {result.expiration_date}")
elif result.is_expired:
    print("License expired")
elif result.hardware_mismatch:
    print("Wrong machine - license bound to different hardware")
else:
    print(f"Invalid: {result.error_message}")

Deactivation with Opportunistic Sync

# Tries to notify server, but succeeds locally even if offline
result = client.deactivate_smart(key_or_code, hwid, "MyProduct")
print(f"Success: {result.success}, Server notified: {result.server_notified}")

Offline Workflow

  1. Customer runs your app and sees their Hardware ID (from generate_hardware_id())
  2. Customer contacts you with their Hardware ID (email/phone/support ticket)
  3. You generate an Offline Activation Code in the License Keys page
  4. Customer enters the code into your app
  5. Your app validates locally using validate_offline() or validate_smart()
Security: Offline codes are RSA-SHA256 signed and embed: product ID, hardware ID, expiration date, email, and feature flags. They cannot be forged or transferred to other machines.

Error Handling

from exisone import ExisOneClient, ExisOneClientOptions
import requests

client = ExisOneClient(options)

try:
    result = client.validate(activation_key, hardware_id)
except requests.HTTPError as e:
    print(f"HTTP error: {e.response.status_code}")
except requests.RequestException as e:
    print(f"Network error: {e}")

Permissions

MethodRequired PermissionNotes
validate()verifyServer enforces in /api/license/validate
generate_activation_key()generatePublisher operations
deactivate()NoneAllowed only when server-side hardware matches bound license
send_support_ticket()emailSends email via tenant/global SMTP
activate()NoneCurrent API does not require a specific scope

API Reference

# Version
get_version() -> str

# Hardware
generate_hardware_id() -> str

# Activation
activate(activation_key: str, email: str, hardware_id: str,
         product_name: str, version: str = None) -> ActivationResult

# Validation (Online)
validate(activation_key: str, hardware_id: str,
         product_name: str = None, version: str = None) -> ValidationResult

# Validation (Offline)
validate_offline(offline_code: str, hardware_id: str) -> OfflineValidationResult
validate_smart(key_or_code: str, hardware_id: str,
               product_name: str = None) -> SmartValidationResult

# Deactivation
deactivate(activation_key: str, hardware_id: str, product_name: str) -> bool
deactivate_smart(key_or_code: str, hardware_id: str,
                 product_name: str) -> DeactivationResult

# Generation (publisher)
generate_activation_key(product_name: str, email: str,
                        plan_id: int = None, validity_days: int = None) -> str

# Support
send_support_ticket(product_name: str, email: str, subject: str, message: str) -> None

# Configuration
with_base_url(base_url: str) -> ExisOneClient

# Result Types
@dataclass
class ActivationResult:
    success: bool
    error_code: str | None
    error_message: str | None
    server_version: str | None
    minimum_required_version: str | None
    license_data: str | None

@dataclass
class ValidationResult:
    is_valid: bool
    status: str
    expiration_date: datetime | None
    features: list[str]
    server_version: str | None
    minimum_required_version: str | None

@dataclass
class OfflineValidationResult:
    is_valid: bool
    error_message: str | None
    product_name: str | None
    expiration_date: datetime | None
    features: list[str]
    is_expired: bool
    hardware_mismatch: bool

@dataclass
class SmartValidationResult:
    is_valid: bool
    status: str
    expiration_date: datetime | None
    features: list[str]
    was_offline: bool
    error_message: str | None
    product_name: str | None
    server_version: str | None
    minimum_required_version: str | None

@dataclass
class DeactivationResult:
    success: bool
    server_notified: bool
    error_message: str | None

Environment Variables

VariableDescription
EXISONE_BASEURLDefault base URL if not specified in options

Dependencies

Compatibility

Common Errors

Changelog

See Also