ZLF Documentation

SDK & APIReference

Complete reference for the Python SDK and all REST API endpoints. Every field documented from the source. All examples use real request and response shapes.

Authentication

The API uses three access levels. Pass your secret in the X-Admin-Secret header.

Public

No authentication required. Called by your customers' running software via the SDK.

Admin

Pass MASTER_SECRET or PRODUCT_SECRET. Most license management operations.

Master Only

Only MASTER_SECRET accepted. Product and plan management — destructive operations.

Example authenticated request
curl -s -X POST https://zlf.yourdomain.com/api/admin/licenses/issue \ -H "Content-Type: application/json" \ -H "X-Admin-Secret: your-master-secret" \ -d '{"email":"customer@example.com","product_id":"my-app","plan_id":"pro"}'

Python SDK

Drop zlf_sdk.py into your product's root directory. No pip install — it's a single file dependency that ships with your product.

Setup

The SDK reads configuration from environment variables set by the installer in /opt/zlf/.env.

VariableExampleDescription
ZLF_LICENSE_KEYAPP-XXXX-XXXX-XXXX-XXXXCustomer's license key. Set by the installer.
ZLF_WORKER_URLhttps://zlf.yourdomain.com/apiBase URL of your ZLF API. No trailing slash.
ZLF_PLANproPlan ID. Set by installer, synced from API on heartbeat.
Minimal startup — app.py
import zlf_sdk # Reads ZLF_LICENSE_KEY and ZLF_WORKER_URL from environment zlf = zlf_sdk.from_env( product_id = "my-app", version = "1.0", ) zlf.startup_validate() zlf.start_heartbeat()
from_env(product_id="my-app", version="1.0")

Constructor. Reads ZLF_LICENSE_KEY and ZLF_WORKER_URL from the environment and returns a configured SDK instance.

  • product_idYour product's ID. Must match what was registered in the portal.
  • versionCurrent version string. Sent with heartbeats for checksum verification.
Returns: ZLFClient instance
zlf.startup_validate()

Call once at application startup. Validates the license key against the API, performs machine binding on first activation, and caches the plan limits locally. If validation fails, calls sys.exit(1) — the app will not start.

On first activation the machine ID is bound in the database and a confirmation email is sent to the license holder. Subsequent calls from the same machine re-sync limits without rebinding.

Returns: None (exits on failure)
zlf.start_heartbeat(interval=600)

Starts a background daemon thread that calls POST /check/heartbeat every interval seconds (default 600 = 10 minutes). The heartbeat sends file checksums and re-syncs limits.

If the heartbeat returns "action": "lock" — due to a revoked license, machine mismatch, or tampered files — the SDK calls sys.exit(1).

  • intervalSeconds between heartbeat calls. Default: 600. Minimum recommended: 60.
Returns: threading.Thread (daemon)
zlf.check_limit(dimension, current_value)

Fast local check using cached limits. No network call. Returns True if current_value is below the plan's limit for that dimension — meaning the action is allowed. Returns True if the dimension is not in the limits (i.e. unlimited).

  • dimensionThe limit key to check. Must match the key in the plan's limits JSON exactly.
  • current_valueThe current count to compare against the limit (e.g. current number of mailboxes).
Returns: bool — True if allowed, False if limit reached
Example
if not zlf.check_limit("mailboxes", db.count_mailboxes()): return jsonify({"error": "Mailbox limit reached"}), 403
zlf.get_limits()

Returns the full limits dictionary cached from the last validate or heartbeat call. Use to show limit usage to your users. An empty dict {} means the plan is unlimited.

Returns: dict — e.g. {"mailboxes": 10, "users": 3}
zlf.check_limits_remote(action, current_state)

Authoritative remote check. Calls POST /check/limits on the API with the current state. Use for high-stakes operations where you need a guaranteed fresh check rather than relying on cached limits.

  • actionDescriptive string for the operation being attempted. Logged server-side.
  • current_stateDict of dimension → current count. All dimensions you want checked.
Returns: dict — {"allowed": bool, "violations": [...], "limits": {...}}
Example
result = zlf.check_limits_remote( action = "add_mailbox", current_state = { "mailboxes": db.count_mailboxes(), "concurrent_jobs": db.count_active_jobs(), } ) if not result.get("allowed"): return jsonify({ "error": "Plan limit reached", "violations": result["violations"] }), 403

Public API

Called by the SDK from your customers' machines. No authentication required. Base URL: https://zlf.yourdomain.com/api

GET /health Public

Health check. Returns {"ok": true} when the API and database are reachable.

Response 200
{ "ok": true, "service": "ZLF" }
POST /validate Public

Validates a license key. On first call with a new machine ID, binds the key to that machine and sends an activation email. Subsequent calls from the same machine re-sync limits. Calls from a different machine return 403.

FieldTypeRequiredDescription
keystringYesThe license key (e.g. APP-XXXX-XXXX-XXXX-XXXX).
product_idstringYesMust match the product the key was issued against.
machine_idstringYesUnique identifier for this machine/server. Derived from hardware by the SDK.
check_onlybooleanNoIf true, validates without binding or logging. Default: false.
Response 200 — valid
{ "valid": true, "email": "customer@example.com", "product_id": "my-app", "plan_id": "pro", "limits": { "mailboxes": 25, "users": 5 }, "expires_at": null }
Response 403 — invalid
{ "valid": false, "message": "License is already activated on another machine. Contact support to transfer." }
POST /check/heartbeat Public

Periodic liveness check called by the SDK every 10 minutes. Verifies the license is still active, the machine ID matches, and optionally checks file integrity. Returns "action": "lock" to shut the app down.

FieldTypeRequiredDescription
keystringYesThe license key.
product_idstringYesProduct ID.
machine_idstringYesMust match the bound machine ID.
versionstringNoApp version string. Required for checksum verification.
checksumsobjectNoMap of filename → SHA-256 hash. Compared against registered checksums.
Response 200 — continue
{ "valid": true, "plan_id": "pro", "limits": { "mailboxes": 25 }, "expires_at": null, "action": "continue" }
Response 403 — lock (tampered files)
{ "valid": false, "message": "File integrity check failed", "tampered_files": ["api/api.py"], "action": "lock" }
POST /check/limits Public

Authoritative remote limit check. Compares current_state against the license's active plan limits and returns violations. Used by zlf.check_limits_remote().

FieldTypeRequiredDescription
keystringYesThe license key.
product_idstringYesProduct ID.
actionstringNoDescriptive name of the operation being checked. Returned in the response.
current_stateobjectYesMap of dimension → current count. All dimensions to check.
Response 200 — allowed
{ "allowed": true, "limits": { "mailboxes": 25 }, "action": "add_mailbox" }
Response 200 — denied
{ "allowed": false, "message": "Plan limit reached", "violations": [{ "dimension": "mailboxes", "current": 25, "limit": 25 }], "limits": { "mailboxes": 25 } }
POST /check/integrity Public

Standalone file integrity check. Used by the installer to verify the downloaded package before running setup. Compares submitted SHA-256 hashes against registered checksums for the given version.

FieldTypeRequiredDescription
keystringYesA valid license key for this product.
product_idstringYesProduct ID.
versionstringYesVersion string matching registered checksums (e.g. "2.0").
checksumsobjectYesMap of filename → SHA-256 hash for every registered file.
Response 200 — clean
{ "valid": true, "unregistered": false }

unregistered: true means no checksums are registered for this version yet. The check passes but you should run release.sh to register checksums before distributing.


Admin API — Licenses

License management endpoints. Accept MASTER_SECRET or PRODUCT_SECRET in X-Admin-Secret header.

POST /admin/licenses/issue Admin

Issues a new license key for a customer. Generates the key, stores it, and sends a license email to the customer via Resend (if configured).

FieldTypeRequiredDescription
emailstringYesCustomer email. License key is emailed here on issue and activation.
product_idstringYesThe product to issue the key for.
plan_idstringYesThe plan the key is issued against. Determines limits.
expires_daysintegerNoDays until expiry. Omit or pass 0 for a perpetual license.
notesstringNoInternal notes. Visible in the portal only.
Response 200
{ "key": "APP-GQE9-NSZM-YGCY-084Z", "email": "customer@example.com", "product_id": "my-app", "plan_id": "pro", "limits": { "mailboxes": 25 }, "expires_at": null }
POST /admin/licenses/revoke Admin

Revokes a license key. The customer's app will receive "action": "lock" on its next heartbeat (within 10 minutes) and call sys.exit(1).

FieldTypeRequiredDescription
keystringYesThe license key to revoke.
Response 200
{ "revoked": true, "key": "APP-XXXX-XXXX-XXXX-XXXX" }
POST /admin/licenses/reactivate Admin

Re-activates a previously revoked license. Cannot reactivate an expired license — extend expiry first.

FieldTypeRequiredDescription
keystringYesThe license key to reactivate.
Response 200
{ "reactivated": true, "key": "APP-XXXX-XXXX-XXXX-XXXX" }
POST /admin/licenses/reset Admin

Clears the machine binding on a license key so it can be activated on a new server. The previous machine ID is logged to the machine_resets audit table.

FieldTypeRequiredDescription
keystringYesThe license key to reset.
reset_bystringNoWho performed the reset. Stored in audit log. Default: "admin".
reasonstringNoReason for reset. Stored in audit log.
Response 200
{ "reset": true, "key": "APP-XXXX-XXXX-XXXX-XXXX" }
POST /admin/licenses/delete Admin

Permanently deletes a license key and all associated validation log entries and machine reset records. Irreversible.

FieldTypeRequiredDescription
keystringYesThe license key to permanently delete.
Response 200
{ "deleted": true, "key": "APP-XXXX-XXXX-XXXX-XXXX" }
GET /admin/licenses/list Admin

Returns a paginated list of licenses. Supports filtering by product and status.

ParamTypeRequiredDescription
product_idstringNoFilter to a specific product.
statusstringNoFilter by status: active, revoked, or expired.
limitintegerNoMax results. Default: 100. Max: 500.
offsetintegerNoPagination offset. Default: 0.
GET /admin/licenses/get?key=APP-XXXX Admin

Returns full details for a single license key including machine ID, activation timestamp, limits snapshot, and heartbeat time.


Admin API — Products

Product management. Requires MASTER_SECRET only — these are destructive operations.

POST /admin/products/create Master Only

Creates a new product. Product ID must be unique lowercase alphanumeric with hyphens only.

FieldTypeRequiredDescription
idstringYesUnique product ID. Lowercase, hyphens only. e.g. imapsync-gui.
namestringYesDisplay name. Used in emails. e.g. "IMAPSync GUI".
worker_urlstringYesYour API base URL. e.g. https://zlf.yourdomain.com/api.
key_prefixstringNoPrefix for generated keys. Default: LIC. e.g. "IMAP" → IMAP-XXXX-XXXX-XXXX-XXXX.
cdn_urlstringNoCDN download URL for the installer.
contact_emailstringNoSupport email shown to customers.
product_secretstringNoSecret for product-scoped admin API access.
GET /admin/products/list Master Only

Returns all products ordered by creation date.

POST /admin/products/update Master Only

Updates product fields. Pass id plus any fields to update: name, worker_url, cdn_url, contact_email, product_secret, key_prefix.

POST /admin/products/delete Master Only

Deletes a product. Blocked if active licenses exist unless force: true is passed.

FieldTypeRequiredDescription
idstringYesProduct ID to delete.
forcebooleanNoPass true to delete even if active licenses exist.

Admin API — Plans

Plan management. Requires MASTER_SECRET. See the Plans & Limits guide for a complete walkthrough.

POST /admin/plans/create Master Only

Creates a new plan for a product.

FieldTypeRequiredDescription
idstringYesPlan slug. Lowercase, hyphens only. e.g. pro.
product_idstringYesThe product this plan belongs to.
namestringYesDisplay name. e.g. "Professional".
limitsobject or stringNoLimits JSON. e.g. {"mailboxes":25}. Default: {} (unlimited).
GET /admin/plans/list?product_id=my-app Master Only

Lists all plans, optionally filtered by product_id.

POST /admin/plans/update Master Only

Updates a plan's name or limits. Limit changes propagate to all active licenses on their next heartbeat — no customer action required.

FieldTypeRequiredDescription
idstringYesPlan ID to update.
namestringNoNew display name.
limitsobject or stringNoNew limits JSON. Replaces existing limits entirely.
POST /admin/plans/delete Master Only

Deletes a plan. Blocked if active licenses are on this plan unless force: true is passed.

FieldTypeRequiredDescription
idstringYesPlan ID to delete.
forcebooleanNoPass true to delete even if active licenses use this plan.

Admin API — Checksums & Stats

Checksum registration is handled automatically by release.sh. These endpoints are documented for manual use or tooling.

POST /admin/checksums/register Admin

Registers SHA-256 checksums for a product version. Uses upsert — safe to re-run. Called by release.sh after packaging.

FieldTypeRequiredDescription
product_idstringYesProduct ID.
versionstringYesVersion string matching the release. e.g. "2.0".
filesobjectYesMap of filename → SHA-256 hex hash.
Example request body
{ "product_id": "my-app", "version": "2.0", "files": { "api/api.py": "a3f2c1...", "setup.sh": "b7e9d4..." } }
GET /admin/checksums/list Admin

Lists registered checksums. Filter with product_id and/or version query params.

GET /admin/checksums/versions Admin

Lists all registered versions and how many files are registered per version. Useful for confirming a release was registered correctly. Filter with product_id query param.

GET /admin/stats Admin

Returns aggregate counts across licenses, products, and plans. Filter to a single product with ?product_id=my-app.

Response 200
{ "licenses": { "total": 42, "active": 38, "revoked": 3, "expired": 1, "activated": 35, // keys that have been bound to a machine "bound": 35 // keys with a machine_id set }, "products": 3, "plans": 9, "product_filter": null }