Client and manager helpers for integrating public validation flows and project operations with the Authlix API.
License client – validates license keys, collects environment metadata and lets end-users update client_editable fields.
Manager client – handles authentication plus CRUD operations for projects, metadata schemas, licenses and API keys.
Cryptographic signing – Ed25519 response signatures, SealedBox HWID encryption and anti-replay protection.
Environment helpers – gather HWID, local/public IPs and other contextual information.
The SDK targets Python 3.9+ and is published to PyPI as authlix.
pip install authlixPyNaCl is included as a dependency and is required for the cryptographic signing features.
from authlix import LicenseClient
client = LicenseClient(
base_url="https://authlix.io",
project_id=42,
)
# Automatically collects HWID as machine fingerprint
result = client.validate_with_environment("CL-XXXX-XXXX")
print(result)
# {"valid": True, "expires_at": "2026-12-31T00:00:00", "metadata": {...}}To control the payload manually, call validate_license and pass hwid, ip, and metadata yourself. The metadata must only contain fields that are marked client_editable in your project schema.
When your project has crypto enabled, the SDK can encrypt the HWID, add anti-replay parameters and automatically verify the Ed25519 signature on every response:
from authlix import LicenseClient
client = LicenseClient(
base_url="https://authlix.io",
project_id=42,
)
# Fetch and cache the project's Ed25519 public key (once per session)
client.fetch_public_key()
# HWID is now encrypted with SealedBox; the response signature is auto-verified
result = client.validate_with_environment("CL-XXXX-XXXX")Or embed the public key directly to avoid the extra round-trip:
client = LicenseClient(
base_url="https://authlix.io",
project_id=42,
public_key="<hex-encoded-ed25519-public-key>",
)LicenseClient constructor options:
| Parameter | Default | Description |
|---|---|---|
public_key |
None |
Hex Ed25519 public key. Enables HWID encryption and signature verification. |
auto_verify |
True |
Verify Ed25519 signature on every response (requires public_key). |
anti_replay |
True |
Include timestamp + nonce in requests (requires public_key). |
from authlix import ManagerClient
manager = ManagerClient(base_url="https://authlix.io")
manager.authenticate("automation_bot", api_key="sk_live_XXXXXXXXXXXXXXXX")
# Create a project
project = manager.create_project(
name="My Awesome App",
description="Tooling pour la communauté",
)
# Create a 30-day license
license_data = manager.create_license(
project_id=project["id"],
days_valid=30,
metadata={"plan": "pro"},
)
# Revoke a license
manager.update_license(
license_id=license_data["id"],
is_active=False,
metadata={"plan": "revoked", "notes": "Chargeback"},
)
# Extend expiration by 14 days
manager.extend_license(project_id=project["id"], license_id=license_data["id"], days=14)
# Delegate day-to-day operations to a teammate
manager.add_project_manager(project_id=project["id"], username="support_team")Prefer get_license_by_key over fetching the full list when you only need one license:
from authlix import ManagerClient, NotFoundError, ForbiddenError
manager = ManagerClient(base_url="https://authlix.io")
manager.authenticate("admin", api_key="sk_live_XXXXXXXXXXXXXXXX")
try:
lic = manager.get_license_by_key(project_id=42, license_key="CL-XXXX-XXXX")
print(f"ID: {lic['id']} active: {lic['is_active']}")
except NotFoundError:
print("Key not found in this project")
except ForbiddenError:
print("Insufficient permissions")The key is automatically normalized to uppercase before lookup.
from authlix import ManagerClient, Scopes
manager = ManagerClient(base_url="https://authlix.io")
manager.authenticate("admin", api_key="sk_live_XXXXXXXXXXXXXXXX")
# Password auth is also supported; in production include hCaptcha token when required.
# manager.authenticate("admin", password="password", hcaptcha_token="<token>")
# Create a key scoped to one project with granular permissions
api_key = manager.create_api_key(
label="CI Runner",
project_ids=[project["id"]],
scopes=[Scopes.LICENSES_READ, Scopes.LICENSES_WRITE],
)
# Expand its scope later
manager.update_api_key(api_key_id=api_key["id"], project_ids=[project["id"], 7])
# Operator-wide audit view
manager.list_api_keys()
# Per-project view
manager.list_project_api_keys(project_id=project["id"])
# Retire when done
manager.delete_api_key(api_key_id=api_key["id"])Available scopes:
| Constant | Description |
|---|---|
Scopes.LICENSES_READ |
Read license data |
Scopes.LICENSES_WRITE |
Create / update / delete licenses |
Scopes.PROJECTS_READ |
Read project settings |
Scopes.PROJECTS_WRITE |
Modify project settings |
Scopes.API_KEYS_MANAGE |
Create / revoke API keys |
Scopes.WEBHOOKS_MANAGE |
Manage webhook endpoints |
Scopes.AUDIT_READ |
Read audit logs |
Scopes.OFFLINE_MANAGE |
Issue / revoke offline tokens |
Scopes.QUOTA_READ |
Read usage and quota data |
Scopes.ALL |
All scopes (list) |
Legacy keys created before scopes were introduced automatically have full access.
Certain metadata fields can be safely mutated by end-users via the client_editable flag.
1. Manager defines the schema:
manager.update_metadata_schema(
project_id=42,
fields=[
{"key": "notes", "type": "string", "client_editable": True},
{"key": "plan", "type": "string", "client_editable": False},
],
)2. Client updates the editable fields:
from authlix import LicenseClient
client = LicenseClient(base_url="https://authlix.io", project_id=42)
client.update_client_metadata(
key="CL-XXXX-XXXX",
metadata={"notes": "Nouvelle machine"},
)update_client_metadata validates locally that project_id is an int and metadata is non-empty before calling POST /api/client_metadata.
Each project has an Ed25519 key pair. The server signs every validation response; the SDK encrypts the HWID with SealedBox and verifies signatures automatically.
manager.toggle_crypto(project_id=42, enabled=True)
manager.toggle_crypto(project_id=42, enabled=False)# Via ManagerClient (authenticated)
data = manager.get_public_key(project_id=42)
public_key_hex = data["public_key"]
# Via LicenseClient (public endpoint, no auth)
client.fetch_public_key() # fetches & caches on client.public_key
print(client.public_key)result = manager.rotate_signing_key(project_id=42)
print(result["signing_public_key"]) # distribute this new key to all SDK clientsWARNING — rotating invalidates all SDK instances still holding the old public key.
from authlix import verify_signature, SignatureVerificationError
try:
verify_signature(public_key_hex, response_payload)
except SignatureVerificationError as exc:
print(f"Signature invalid: {exc}")from authlix import collect_environment_metadata, get_machine_fingerprint, get_public_ip
# Stable machine fingerprint (SHA-256 of MAC + platform info)
hwid = get_machine_fingerprint()
# Public IP via ipify
ip = get_public_ip()
# Both at once (public IP optional)
meta = collect_environment_metadata(include_public_ip=True)
print(meta) # {"hwid": "...", "ip": "..."}Subscribe to license lifecycle events (created, activated, expired, revoked, quota exceeded) and receive real-time HTTP callbacks.
from authlix import ManagerClient
manager = ManagerClient(base_url="https://authlix.io")
manager.authenticate("admin", api_key="sk_live_XXXXXXXXXXXXXXXX")
# Register a webhook endpoint
webhook = manager.create_webhook(
project_id=42,
url="https://my-app.io/hooks/authlix",
events=["license.created", "license.revoked", "quota.exceeded"],
secret="whsec_my_signing_secret", # optional HMAC secret
)
print(f"Webhook #{webhook['id']} registered")
# List endpoints for a project
webhooks = manager.list_webhooks(project_id=42)
# Update events or URL
manager.update_webhook(webhook_id=webhook["id"], events=["license.created"])
# Send a test ping
manager.test_webhook(webhook_id=webhook["id"])
# View delivery history
deliveries = manager.list_webhook_deliveries(webhook_id=webhook["id"])
# Remove when no longer needed
manager.delete_webhook(webhook_id=webhook["id"])Payloads are signed with HMAC-SHA256 when a secret is provided. Verify the X-Authlix-Signature header on your server.
Every significant action (license CRUD, API key changes, webhook updates) is recorded in an append-only audit log.
# List recent events for a project
logs = manager.list_audit_logs(project_id=42, page=1, per_page=50)
for entry in logs["items"]:
print(f"{entry['timestamp']} {entry['action']} by {entry['actor']}")
# Export full audit trail as CSV or JSON
csv_data = manager.export_audit_logs(project_id=42, fmt="csv")
with open("audit.csv", "w") as f:
f.write(csv_data)
json_data = manager.export_audit_logs(project_id=42, fmt="json")Projects can define quota policies (max activations, max devices, grace periods). The SDK exposes usage summaries.
from authlix import QuotaExceededError
# View usage summary for a license
usage = manager.get_usage_summary(license_id=123)
print(f"Activations: {usage['activation_count']}/{usage['max_activations']}")
print(f"Devices: {usage['device_count']}/{usage['max_devices']}")
# Quota enforcement happens automatically during validation
from authlix import LicenseClient
client = LicenseClient(base_url="https://authlix.io", project_id=42)
try:
result = client.validate_with_environment("CL-XXXX-XXXX")
quota = result.get("quota_info", {})
print(f"Quota status: {quota.get('quota_status')}")
except QuotaExceededError as exc:
print(f"Quota exceeded: {exc}")Issue time-limited Ed25519-signed tokens for environments without Internet access. Tokens can be verified locally without contacting the server.
from authlix import ManagerClient, LicenseClient
manager = ManagerClient(base_url="https://authlix.io")
manager.authenticate("admin", api_key="sk_live_XXXXXXXXXXXXXXXX")
# Issue an offline token (server-side)
result = manager.issue_offline_token(
license_id=123,
machine_hash="sha256-of-machine-fingerprint",
ttl_days=90,
)
offline_token = result["token"]
print(f"Token valid until: {result['expires_at']}")
# Verify locally on the client machine (no network needed)
payload = LicenseClient.verify_offline_token_locally(
offline_token,
public_key_hex="<project-ed25519-public-key>",
)
print(f"License #{payload['license_id']} verified offline until {payload['exp']}")
# Revoke a compromised token (server-side)
manager.revoke_offline_token(token_id=result["id"])
# List all offline tokens for a license
tokens = manager.list_offline_tokens(license_id=123)from authlix import (
AuthlixError,
AuthenticationError,
ApiError,
BadRequestError,
ForbiddenError,
NotFoundError,
QuotaExceededError,
OfflineTokenError,
SignatureVerificationError,
)
try:
result = client.validate_with_environment("CL-XXXX-XXXX")
except SignatureVerificationError as exc:
# Ed25519 signature mismatch — possible tampering
print(f"Signature invalid: {exc}")
except QuotaExceededError as exc:
# License exceeded activation/device limits
print(f"Quota exceeded (429): {exc}")
except BadRequestError as exc:
print(f"Bad request (400): {exc}")
except ForbiddenError as exc:
print(f"HWID mismatch or inactive license (403): {exc}")
except NotFoundError as exc:
print(f"License not found (404): {exc}")
except ApiError as exc:
print(f"API error {exc.status_code}: {exc} payload={exc.payload}")
except AuthlixError as exc:
print(f"SDK error: {exc}")| Exception | HTTP | When |
|---|---|---|
BadRequestError |
400 | Missing / invalid parameters |
ForbiddenError |
403 | HWID mismatch, inactive license, insufficient scope |
NotFoundError |
404 | Unknown license key or project |
QuotaExceededError |
429 | Activation or device limit exceeded |
OfflineTokenError |
— | Offline token expired, revoked, or signature invalid |
SignatureVerificationError |
— | Ed25519 signature verification failed |
ApiError |
≥400 | Any other server error |
AuthenticationError |
— | Login failed or JWT missing |
AuthlixError |
— | Base class for all SDK exceptions |
| Method | Description |
|---|---|
| Auth | |
authenticate(username, *, password, api_key, hcaptcha_token) |
Login and store JWT |
| Projects | |
list_projects() |
List all accessible projects |
create_project(name, description) |
Create a new project |
add_project_manager(project_id, username) |
Delegate back-office access |
update_metadata_schema(project_id, fields) |
Define project metadata schema |
| Licenses | |
list_licenses(project_id) |
List all licenses for a project |
get_license_by_key(project_id, license_key) |
Efficient single-key lookup |
create_license(project_id, days_valid, metadata) |
Generate a new license |
update_license(license_id, *, is_active, expires_at, reset_hwid, metadata) |
Update a license |
extend_license(project_id, license_id, days) |
Push expiration forward |
delete_license(license_id) |
Hard-delete a license |
| API Keys | |
list_api_keys() |
Operator-wide API key list |
create_api_key(label, *, project_ids, allow_all_projects, scopes) |
Mint a scoped API key |
update_api_key(api_key_id, *, label, project_ids, allow_all_projects) |
Update key scope |
delete_api_key(api_key_id) |
Revoke an API key |
list_project_api_keys(project_id) |
Per-project API key list |
| Crypto | |
get_public_key(project_id) |
Fetch Ed25519 public key (no auth) |
toggle_crypto(project_id, enabled) |
Enable/disable signing (owner) |
rotate_signing_key(project_id) |
Regenerate key pair (owner) |
| Webhooks | |
list_webhooks(project_id) |
List webhook endpoints |
create_webhook(project_id, url, *, events, secret) |
Register a webhook |
update_webhook(webhook_id, *, url, events, is_active) |
Update a webhook |
delete_webhook(webhook_id) |
Remove a webhook |
test_webhook(webhook_id) |
Send a test ping |
list_webhook_deliveries(webhook_id) |
View delivery history |
| Audit | |
list_audit_logs(project_id, *, page, per_page) |
Browse audit events |
export_audit_logs(project_id, *, fmt) |
Export as csv or json |
| Quotas | |
get_usage_summary(license_id) |
Activation/device usage stats |
| Offline Activation | |
issue_offline_token(license_id, machine_hash, *, ttl_days) |
Generate offline token |
revoke_offline_token(token_id) |
Revoke a token |
list_offline_tokens(license_id) |
List tokens for a license |
| Method | Description |
|---|---|
validate_license(key, *, hwid, ip, metadata) |
Validate a license key |
validate_with_environment(key) |
Validate with auto-collected HWID/IP |
update_client_metadata(key, metadata) |
Update client-editable fields |
fetch_public_key() |
Fetch and cache Ed25519 public key |
verify_offline_token_locally(token, public_key_hex) |
Verify offline token without network (static) |
See the examples/ directory for runnable scripts:
examples/client/— validation, crypto, metadata update, offline tokens, error handlingexamples/manager/— project setup, license management, API keys, crypto, webhooks, audit logs, quotas, offline activation, scopes
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e .
python -m pytest tests/ -v
python -m build