Skip to main content

Python SDK for Licentric — license keys, machine activation, offline validation, AI agent monetization, and Stripe-driven license provisioning. Alternative to Keygen, Cryptlex, LemonSqueezy License Keys.

Project description

Licentric Python SDK

License: MIT

Official Python client for the Licentric software licensing API. Validate licenses, manage machine activations, send heartbeats, and check out offline license files.

Installation

pip install licentric

Requirements: Python 3.9+

Install from source (for contributors)

cd sdks/python
pip install -e .

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)

Response Signature Verification

The Licentric backend signs validation responses with HMAC-SHA256 over the raw response body. The SDK verifies the X-Signature and X-Signature-Timestamp headers before trusting the result, defeating MITM attackers who would otherwise flip valid: False -> valid: True in flight.

from licentric import Licentric, SignatureVerificationError

client = Licentric(
    api_key="lk_live_...",
    # Returned alongside `plain_key` by POST /api/dashboard/api-keys.
    # Same value for every API key in the account; show it to the user once.
    # IMPORTANT: this is the **response** signing key (HMAC over validation
    # responses). It is NOT the same as the webhook signing secret
    # (`whsec_...`) returned when you create a webhook endpoint. Passing a
    # `whsec_...` value here will trigger a startup WARNING and break every
    # signature check.
    signing_key="abcd1234...",  # hex, 32 bytes
)

try:
    result = client.validate(key="LIC-XXXX")
except SignatureVerificationError:
    # Hard failure — the response was tampered with, replayed, or unsigned.
    # Never grace-eligible: a forged `valid=True` is worse than refusing
    # to validate. The SDK refuses to write unverified responses to the cache.
    ...

Defaults. When signing_key is supplied, verification runs automatically. When it is not, the SDK skips verification and logs a WARNING — backward-compatible for clients that haven't yet upgraded their key handling but explicitly noisy so the gap is visible. To disable verification while your signing_key is configured (rarely needed), pass verify_signature=False.

Replay protection. Responses with X-Signature-Timestamp more than 5 minutes off the local clock are rejected.

Cache integrity. A failed signature verification short-circuits before the cache write, so a forged response cannot poison the offline cache.

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)
SignatureVerificationError SIGNATURE_INVALID Response signature failed HMAC check (MITM, replay, or stripped header) — never grace-eligible

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, SignatureVerificationError

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?) None (public) Validate a license key (key passed in body, no auth header)
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


Download files

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

Source Distribution

licentric-0.3.0.tar.gz (58.6 kB view details)

Uploaded Source

Built Distribution

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

licentric-0.3.0-py3-none-any.whl (28.4 kB view details)

Uploaded Python 3

File details

Details for the file licentric-0.3.0.tar.gz.

File metadata

  • Download URL: licentric-0.3.0.tar.gz
  • Upload date:
  • Size: 58.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.6

File hashes

Hashes for licentric-0.3.0.tar.gz
Algorithm Hash digest
SHA256 3c5d4a22346be442e68cd4c2a9af79facc0423c60103fdf37c5dd47added3fa5
MD5 5d90166f790226e3fc5769fd58ad83cb
BLAKE2b-256 e28a4239d0277dd711806d1b17fe0df3755659c855953b19de3514eda0df8a50

See more details on using hashes here.

File details

Details for the file licentric-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: licentric-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 28.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.6

File hashes

Hashes for licentric-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2747c5fa56b01269100782d8a5e6101a955efb40f5ac850f185ab7afaf405563
MD5 1a8a6e0d74500d6439d8fea609c6b8f4
BLAKE2b-256 56e90e0ec6329d5769dcc7e29b452ccfdc59079915de049fadcff3f89d548911

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