Client SDK for Licers — verify software license keys with tamper-proof, signed responses.
Project description
pylicensify
Client SDK for Licers — verify software license keys with tamper-proof, signed responses. A cracked "fake server" can't grant access, because every response is verified against your public key.
pip install pylicensify
Brand vs. endpoint: Licers lives at licers.com (dashboard, docs, account). The SDK talks to the validation API at
https://license.pyobfuscate.comby default — a stable, long-lived host your shipped apps rely on. Override it only for self-hosting (see below).
Quick start
Get your public key from the Licers dashboard → Integration page (safe to embed in your distributed app; the private key never leaves the server).
from pylicensify import LicenseClient
client = LicenseClient(public_key="YOUR_PUBLIC_KEY")
result = client.validate("PY-XXXXXXXXXXXXXXXX")
if result:
print("Licensed. Entitlements:", result.features)
if result.feature("pro"):
enable_pro_features()
else:
print("Not licensed:", result.error)
raise SystemExit(1)
validate() sends a nonce, verifies the Ed25519 signature, and rejects stale /
replayed responses automatically. It raises SignatureError on tampering and
NetworkError if the server is unreachable.
Feature gating
result = client.validate(key)
seats = result.feature("seats", 1)
if result.feature("pro"):
...
Offline license files (air-gapped)
Issue a signed .lic file from a license row in the dashboard, ship it with the
app, and verify it locally with zero network calls:
from pylicensify import verify_offline_license
data = verify_offline_license("YOUR_PUBLIC_KEY", "license.lic")
print("Valid for:", data.get("customer"), data.get("features"))
Checks signature, expiry, device binding, and OS lock.
Floating / concurrent licenses
For keys with a concurrent-session limit — the SDK checks out a seat, heartbeats in the background, and releases on exit:
from pylicensify import LicenseClient, SeatUnavailable
client = LicenseClient(public_key="YOUR_PUBLIC_KEY")
try:
with client.session("PY-XXXX...") as sess:
print("Seat acquired:", sess.seats) # {'used': 1, 'max': 5}
run_app() # released automatically on exit
except SeatUnavailable:
print("All seats are in use — try again later.")
Update checks
info = client.check_update("PY-XXXX...", current_version="1.0.0")
if info["update_available"]:
print("New version:", info["version"], info["notes"])
# info["download_url"] is a license-gated download link
Self-hosted / custom domain
By default the SDK calls the managed cloud API at https://license.pyobfuscate.com
(your account and dashboard live at licers.com). To target a
self-hosted deployment, pass api_url:
client = LicenseClient(public_key="...", api_url="https://license.yourdomain.com")
Hardware ID
By default the SDK uses a stable per-installation device id: the machine's
MAC address where one is readable, otherwise a random id persisted to disk and
reused on every run. This keeps the same device identity across runs on all
platforms — including Android/Pydroid, VMs, and containers, where the MAC is not
reliable and a naive uuid.getnode() changes on every run (which would trip the
max-device limit).
The persisted id lives under a per-user data dir (%LOCALAPPDATA%\pylicensify
on Windows, ~/.local/share/pylicensify elsewhere); override the location with
the PYLICENSIFY_HWID_DIR environment variable. Pass your own for a
stronger/OS-specific fingerprint:
client.validate(key, hwid=my_custom_fingerprint())
Errors
| Exception | Meaning |
|---|---|
InvalidLicense |
Server rejected the key (invalid/expired/revoked/blocked) |
SignatureError |
Signature/nonce/timestamp failed — forged or replayed |
NetworkError |
Couldn't reach the license server |
SeatUnavailable |
Floating license has no free seats |
All inherit from LicenseError.
Hardening
Obfuscate your client so the check itself can't be trivially patched out — e.g. with PyObfuscate.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pylicensify-1.0.2.tar.gz.
File metadata
- Download URL: pylicensify-1.0.2.tar.gz
- Upload date:
- Size: 10.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1fe4b06a9b7d179ca21c855d82782aac0423c8f0fa929960d25396ca792837ad
|
|
| MD5 |
3f99c092be52ba45f216a29a42c1c303
|
|
| BLAKE2b-256 |
54d0554718c88108ce83eb883ca7df4f64621183c0a318c03d58bbb643fee3de
|
File details
Details for the file pylicensify-1.0.2-py3-none-any.whl.
File metadata
- Download URL: pylicensify-1.0.2-py3-none-any.whl
- Upload date:
- Size: 9.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e23b48e85024967793e3ad72bbc99ee48b7f4cb62719bf9e20f6e7ce9594c060
|
|
| MD5 |
5fd53f51d7644ff85f67fb4ce600b17a
|
|
| BLAKE2b-256 |
29776f0bc61a63834eefc45e0d8d45a5fc0d05dd56bdb97a03deb3a037324204
|