Skip to main content

Client and manager helpers for Authlix

Project description

Authlix Python SDK

PyPI version Python Downloads License Docs

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 handling
  • examples/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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

authlix-0.5.0.tar.gz (27.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

authlix-0.5.0-py3-none-any.whl (21.3 kB view details)

Uploaded Python 3

File details

Details for the file authlix-0.5.0.tar.gz.

File metadata

  • Download URL: authlix-0.5.0.tar.gz
  • Upload date:
  • Size: 27.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for authlix-0.5.0.tar.gz
Algorithm Hash digest
SHA256 36ab9dad9d9fa782a0fc50affc6b9ff9ca752231d31acd5dc8a9478a0a1fd0c6
MD5 931981f3e2386d6dc26cbf464bd37e3a
BLAKE2b-256 2328cef0b119705b2e1103a22445cd5df1cfe56d1bbb709ce1e77eec11550efe

See more details on using hashes here.

File details

Details for the file authlix-0.5.0-py3-none-any.whl.

File metadata

  • Download URL: authlix-0.5.0-py3-none-any.whl
  • Upload date:
  • Size: 21.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for authlix-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 40e49d6b419e935b81d66be81d2309b79622cd58a7ec5c451c0794a07f50304b
MD5 70324a699558a607b495a9ed4c771d8f
BLAKE2b-256 437aec3dee747c8f30f59dbd8d6ce075caf080c1f1e5015e735dd7137abdf229

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page