Python SDK for the Licentric software licensing API
Project description
Licentric Python SDK
Official Python client for the Licentric software licensing API. Validate licenses, manage machine activations, send heartbeats, and check out offline license files.
Note: This package is not yet published to PyPI. Install from source (see below).
Installation
# Install from source (not yet published to PyPI)
cd sdks/python
pip install -e .
Requirements: Python 3.9+
Quick Start
from licentric import Licentric
client = Licentric(api_key="lk_live_...")
# Validate a license key
result = client.validate(key="LIC-XXXX-XXXX-XXXX")
if result.valid:
print(f"License is valid — code: {result.code}")
Machine Activation
from licentric import Licentric
from licentric.fingerprint import fingerprint
client = Licentric(api_key="lk_live_...")
# Activate a machine
machine = client.activate(
key="LIC-XXXX-XXXX-XXXX",
fingerprint=fingerprint(),
name="production-server",
platform="linux",
)
print(f"Activated machine: {machine.id}")
# Send heartbeat to keep the machine alive
client.heartbeat(machine_id=machine.id, key="LIC-XXXX-XXXX-XXXX")
# Deactivate when done
client.deactivate(machine_id=machine.id, key="LIC-XXXX-XXXX-XXXX")
Fetching License Details
get_license and get_license_by_key return the full license — including
all activated machines and the bound policy snapshot — in a single API call.
Useful for dashboards, admin UIs, and any flow that needs to inspect the
ruleset without making three sequential requests.
client = Licentric(api_key="lk_live_...")
# By internal license ID
license = client.get_license("lic_abc123")
# Or by raw license key (URL-encoded server-side)
license = client.get_license_by_key("LIC-XXXX-XXXX-XXXX-XXXX")
print(f"Status: {license.status}")
print(f"Active machines: {len(license.machines or [])}")
print(f"Max machines allowed: {license.policy.max_machines}")
print(f"Offline allowed: {license.policy.offline_allowed}")
print(f"Feature flags: {license.policy.metadata.get('feature_flags', [])}")
The raw key and key_hash fields are stripped server-side — License
returned here never contains the secret material.
Offline License Files
Check out a signed license file for offline validation:
license_file = client.checkout(
license_id="uuid-of-license",
fingerprint=fingerprint(),
ttl=1209600, # 14 days
)
print(f"Certificate: {license_file.certificate}")
Offline Caching
Cache validation results locally with HMAC-SHA256 tamper detection:
from licentric.cache import ValidationCache
cache = ValidationCache()
# Store a validation result (default TTL: 15 minutes)
cache.set(key_hash="sha256_of_license_key", result={"valid": True, "code": "VALID"})
# Retrieve (returns None if expired or tampered)
cached = cache.get(key_hash="sha256_of_license_key")
if cached:
print(cached.data)
Error Handling
All API errors inherit from LicentricError with structured fields:
from licentric import (
Licentric,
AuthenticationError,
LicenseRevokedError,
LicenseSuspendedError,
LicenseExpiredError,
NotFoundError,
RateLimitError,
)
import time
client = Licentric(api_key="lk_live_...")
try:
machine = client.activate(key="LIC-XXXX", fingerprint="abc123")
except LicenseRevokedError:
# Permanent — show "license has been revoked, contact support"
print("License revoked")
except LicenseSuspendedError:
# Temporary — usually a payment issue; surface "billing on hold"
print("License suspended")
except LicenseExpiredError:
# Renewal flow
print("License expired — please renew")
except RateLimitError as e:
# Retry-eligible after backoff
time.sleep(e.retry_after)
except AuthenticationError:
# Configuration problem — fail immediately
print("Invalid API key")
HTTP-status exceptions (raised by HTTP code)
| Exception | HTTP Status | When |
|---|---|---|
AuthenticationError |
401 | Invalid or missing API key |
ForbiddenError |
403 | Authenticated but lacks permission/scope |
NotFoundError |
404 | Resource does not exist |
ValidationError |
400 | Invalid request parameters (details has Zod issues) |
ConflictError |
409 | Generic state conflict (no specific license-state code) |
RateLimitError |
429 | Too many requests (use retry_after) |
LicentricError |
5xx + other | Base class — also raised for unknown statuses |
License-state exceptions (raised by body code, regardless of HTTP status)
These subclass LicentricError directly — NOT the HTTP-status classes — so
catching ConflictError will NOT catch them. Catch by license-state class
explicitly:
| Exception | API code | When |
|---|---|---|
LicenseRevokedError |
LICENSE_REVOKED |
License has been revoked (permanent) |
LicenseSuspendedError |
LICENSE_SUSPENDED |
License is suspended (e.g. payment failure — temporary) |
LicenseExpiredError |
LICENSE_EXPIRED |
License past expiration and policy disallows continued use |
LicenseBannedError |
LICENSE_BANNED |
License banned for abuse (terminal — no recovery) |
Recovery classification — what to retry vs fail immediately
| Strategy | Exceptions |
|---|---|
| Grace-eligible (use cached validation if available, retry on next request) | Network errors, 5xx LicentricError, RateLimitError after backoff completes |
| Fail immediately (do not retry; surface to end user) | LicenseRevokedError, LicenseSuspendedError, LicenseExpiredError, LicenseBannedError, AuthenticationError, NotFoundError, ValidationError |
API Reference
Licentric(api_key, base_url?, timeout?)
| Parameter | Type | Default | Description |
|---|---|---|---|
api_key |
str |
— | Your API key (lk_live_... or lk_test_...) |
base_url |
str |
https://licentric.com |
API base URL |
timeout |
float |
30.0 |
Request timeout in seconds |
Methods
| Method | Auth | Description |
|---|---|---|
validate(key, fingerprint?, entitlements?) |
License key | Validate a license key |
activate(key, fingerprint, name?, platform?) |
License key | Activate a machine |
deactivate(machine_id, key) |
License key | Remove a machine activation |
heartbeat(machine_id, key) |
License key | Send a machine heartbeat |
checkout(license_id, fingerprint, ttl?) |
API key | Check out an offline license file |
close() |
— | Close the HTTP client |
The client supports context manager usage:
with Licentric(api_key="lk_live_...") as client:
result = client.validate(key="LIC-XXXX-XXXX-XXXX")
Machine Fingerprinting
The fingerprint() function generates a stable SHA-256 hash identifying the current machine:
| Platform | Source |
|---|---|
| macOS | Hardware UUID via ioreg |
| Linux | /etc/machine-id or Docker container ID |
| Windows | Product UUID via wmic |
| Fallback | hostname:username hash |
Type Safety
This package includes a py.typed marker (PEP 561) and ships with full type annotations. Works with mypy, pyright, and other type checkers out of the box.
Documentation
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file licentric-0.1.0.tar.gz.
File metadata
- Download URL: licentric-0.1.0.tar.gz
- Upload date:
- Size: 39.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
301e10612f1ec9e10f441e416e1c7eb31521e4de2571db94524bd629f3a990c8
|
|
| MD5 |
2cc495e3a5313fea6ea549d68f38c4f7
|
|
| BLAKE2b-256 |
07d441f48f4f218903508238b21960c03579b88c7758a199e88f0c489ffbf7cb
|
File details
Details for the file licentric-0.1.0-py3-none-any.whl.
File metadata
- Download URL: licentric-0.1.0-py3-none-any.whl
- Upload date:
- Size: 20.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
23815470c112c7f1eb25ece6709e0ba0d9a81ba7411b8fda1c0697c45154b6ea
|
|
| MD5 |
629ea5be29fca6678b1b82cf30fb166c
|
|
| BLAKE2b-256 |
f798ec494b5fcd3bd431692e2d0cdab92b0009eaaf1b8b4d8b547fcc3fa5bc20
|