Skip to main content

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

Metadata

Release files for licentric 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for licentric 0.3.0
File Size Uploaded
licentric-0.3.0.tar.gz 58.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for licentric 0.3.0
File Interpreter ABI Platform
licentric-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 87.1 kB

Release files / licentric-0.3.0.tar.gz

Download URL licentric-0.3.0.tar.gz
Size 58.6 kB
Tags Source
SHA-256 checksum
How to use checksums
3c5d4a22346be442e68cd4c2a9af79facc0423c60103fdf37c5dd47added3fa5
BLAKE2b-256 checksum
How to use checksums
e28a4239d0277dd711806d1b17fe0df3755659c855953b19de3514eda0df8a50
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.9.6

Release files / licentric-0.3.0-py3-none-any.whl

Download URL licentric-0.3.0-py3-none-any.whl
Size 28.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2747c5fa56b01269100782d8a5e6101a955efb40f5ac850f185ab7afaf405563
BLAKE2b-256 checksum
How to use checksums
56e90e0ec6329d5769dcc7e29b452ccfdc59079915de049fadcff3f89d548911
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.9.6

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page