ExisOne Python SDK
Embed our cross-platform Python library to generate hardware IDs, activate and validate licenses, and send support tickets.
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
- Hardware ID:
generate_hardware_id()— cross-platform fingerprint (salted SHA-256) - Activate:
activate(key, email, hardware_id, product_name, version=None)→ returnsActivationResultwith success/error details and server version info - Validate:
validate(key, hardware_id, product_name=None, version=None)→ returnsValidationResultwith status, expiration, features - Deactivate:
deactivate(key, hardware_id, product_name)(requires the same hardware) - Generate key:
generate_activation_key(product_name, email, plan_id=None, validity_days=None)(requiresgeneratepermission) - Support ticket:
send_support_ticket(product_name, email, subject, message)(requiresemailpermission) - Offline Validation:
validate_offline(offline_code, hardware_id)→ validates locally without server connectionvalidate_smart(key_or_code, hardware_id, product_name=None)→ auto-detects online/offline, falls back gracefullydeactivate_smart(key_or_code, hardware_id, product_name)→ opportunistic server sync
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
- Customer runs your app and sees their Hardware ID (from
generate_hardware_id()) - Customer contacts you with their Hardware ID (email/phone/support ticket)
- You generate an Offline Activation Code in the License Keys page
- Customer enters the code into your app
- Your app validates locally using
validate_offline()orvalidate_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
| Method | Required Permission | Notes |
|---|---|---|
validate() | verify | Server enforces in /api/license/validate |
generate_activation_key() | generate | Publisher operations |
deactivate() | None | Allowed only when server-side hardware matches bound license |
send_support_ticket() | email | Sends email via tenant/global SMTP |
activate() | None | Current 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
| Variable | Description |
|---|---|
EXISONE_BASEURL | Default base URL if not specified in options |
Dependencies
requests— HTTP clientcryptography— RSA signature verification for offline validation
Compatibility
- Python 3.8, 3.9, 3.10, 3.11, 3.12, 3.13
- Windows, Linux, macOS
- Full type hints (PEP 561 compliant)
Common Errors
- 403 verify permission required: Add
verifyto the token in Access Tokens UI. - Activation key not found in this tenant: Token and key must belong to same tenant.
- 400 Bad Request on activation: Ensure product name is correct.
- version_outdated: Client version is below the minimum required. Check
minimum_required_versionin the response and prompt user to upgrade.
Changelog
- 0.7.0: Initial Python SDK release with full feature parity to .NET SDK.
See Also
- .NET SDK Documentation — If you're building with C#/.NET
- REST API Documentation — Direct API integration
- PyPI Package Page