Authlix Python SDK
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.
Installation
pip install authlix
PyNaCl is included as a dependency and is required for the cryptographic signing features.
Quick start
Validate a license key
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.
Validate with cryptographic security
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). |
Manage projects and licenses
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")
Look up a license by key
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.
API key management with scopes
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.
Client-editable metadata
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.
Cryptographic signing (Ed25519)
Each project has an Ed25519 key pair. The server signs every validation response; the SDK encrypts the HWID with SealedBox and verifies signatures automatically.
Enable / disable crypto (owner only)
manager.toggle_crypto(project_id=42, enabled=True)
manager.toggle_crypto(project_id=42, enabled=False)
Retrieve the public key
# 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)
Rotate the signing key (owner only)
result = manager.rotate_signing_key(project_id=42)
print(result["signing_public_key"]) # distribute this new key to all SDK clients
WARNING — rotating invalidates all SDK instances still holding the old public key.
Manual signature verification
from authlix import verify_signature, SignatureVerificationError
try:
verify_signature(public_key_hex, response_payload)
except SignatureVerificationError as exc:
print(f"Signature invalid: {exc}")
Environment helpers
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": "..."}
Webhooks
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.
Audit logs
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")
Quotas & usage limits
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}")
Offline activation
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)
Error handling
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 |
ManagerClient reference
| 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 |
LicenseClient reference
| 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) |
Examples
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
Local development
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e .
python -m pytest tests/ -v
python -m build
Metadata
Release files for authlix 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| authlix-0.5.0.tar.gz | 27.4 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| authlix-0.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 48.7 kB
Release files / authlix-0.5.0.tar.gz
| Download URL | authlix-0.5.0.tar.gz |
|---|---|
| Size | 27.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
36ab9dad9d9fa782a0fc50affc6b9ff9ca752231d31acd5dc8a9478a0a1fd0c6
|
|
BLAKE2b-256 checksum How to use checksums |
2328cef0b119705b2e1103a22445cd5df1cfe56d1bbb709ce1e77eec11550efe
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.7
|
Release files / authlix-0.5.0-py3-none-any.whl
| Download URL | authlix-0.5.0-py3-none-any.whl |
|---|---|
| Size | 21.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
40e49d6b419e935b81d66be81d2309b79622cd58a7ec5c451c0794a07f50304b
|
|
BLAKE2b-256 checksum How to use checksums |
437aec3dee747c8f30f59dbd8d6ce075caf080c1f1e5015e735dd7137abdf229
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.7
|
Security for authlix 0.5.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.