Skip to main content

Python client for Keysat — a Bitcoin-native self-hosted software licensing service that runs on Start9. Verifies signed license keys offline and wraps the HTTP API for purchase, redemption, and revocation checks.

Project description

keysat-licensing-client (Python)

Python client for Keysat — a Bitcoin-native self-hosted software licensing service that runs on Start9.

Verifies signed license keys offline using the issuer's public key, and (optionally) wraps the HTTP API for live validation, purchase, and free-license redemption.

Install

pip install keysat-licensing-client            # offline only
pip install keysat-licensing-client[online]    # + httpx for the online client

Requires Python 3.10+.

Five-line offline check

import time
from keysat_licensing_client import Verifier, PublicKey

ISSUER_PUBKEY_PEM = open("assets/issuer.pub").read()  # bake this into your app
verifier = Verifier(PublicKey.from_pem(ISSUER_PUBKEY_PEM))

# verify_with_time checks the signature AND rejects an expired key in one
# call (perpetual keys, expires_at == 0, never expire). Use verify() if
# you'd rather inspect an expired key than reject it.
ok = verifier.verify_with_time(key_from_user, int(time.time()))  # raises kind="expired" if past expiry
# ok.expires_at is a unix timestamp; 0 = perpetual
print(f"licensed for product {ok.product_id}, expires {ok.expires_at}")

Online check (with revocation + fingerprint binding)

from keysat_licensing_client import Client

client = Client("https://license.example.com")
r = client.validate(
    key_from_user,
    product_slug="my-product",
    fingerprint="machine-fingerprint",
)
if not r.ok:
    print("server rejected:", r.reason)
    # 'revoked', 'fingerprint_mismatch', 'not_found', 'product_mismatch', etc.

The recommended pattern is offline-first, online-augmented: do the offline Verifier.verify() at boot. If that succeeds, also do an async/background client.validate() to catch revocations and seat mismatches. If the network fails, treat it as "status unknown" — don't gate the user on your server's uptime.

Purchase flow (drives the whole BTCPay round trip)

from keysat_licensing_client import Client, StartPurchaseOptions
import webbrowser

client = Client("https://license.example.com")
session = client.start_purchase(
    "my-product",
    StartPurchaseOptions(buyer_email="bob@example.com"),
)
webbrowser.open(session.checkout_url)
license_key = client.wait_for_license(session.invoice_id)
# Save license_key wherever you decided to store keys (config dir, keychain, env).

To buy a specific tier, set StartPurchaseOptions(policy_slug=...) to a slug from list_public_policies (below); omit it to use the product's default policy.

Tier picker (public policies)

List the buyer-visible tiers for a product — same data the server's /buy/<slug> page reads, so an in-app picker stays in sync with the operator's admin setup. No auth required.

tiers = client.list_public_policies("my-product")
for p in tiers.policies:
    print(p.slug, p.name, p.price_sats, "sats", p.max_machines, "seats")
# tiers.product.entitlements_catalog maps entitlement slugs -> human labels.

Machine seat management

For seat-limited licenses (max_machines > 1), claim and release seats by fingerprint. Each returns a MachineResponse (ok, reason, active_count, max_machines).

client.activate(key, fingerprint, hostname="bob-laptop", platform="macos")
client.heartbeat(key, fingerprint)   # call periodically to keep the seat live
client.deactivate(key, fingerprint)  # release the seat

Free-license code redemption

For codes the seller created with kind free_license (no payment):

result = client.redeem_free_license(
    "my-product",
    "PRESSPASS",
)
print("redeemed:", result.license_key)

Fingerprint binding

The SDK doesn't decide WHAT to use as a fingerprint — that's a product choice. Common sources, ordered by robustness:

  • Linux: /etc/machine-id
  • macOS: ioreg -d2 -c IOPlatformExpertDevice
  • Windows: registry MachineGuid
  • Fallback: random UUID written into your app's config dir on first run

Mix in a per-product salt so fingerprints from your app can't be replayed against someone else's licensing server:

fp_input = f"{APP_NAME}|{machine_id}"
# Pass this raw string to validate(...); the server SHA-256s it before storing.

License

MIT OR Apache-2.0. See the upstream LICENSE file at github.com/keysat-xyz/keysat.

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

keysat_licensing_client-0.3.0.tar.gz (17.2 kB view details)

Uploaded Source

Built Distribution

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

keysat_licensing_client-0.3.0-py3-none-any.whl (16.0 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for keysat_licensing_client-0.3.0.tar.gz
Algorithm Hash digest
SHA256 ed2063b8fea8497de5a5121582bc142ffe8cf2ecddf60ea91edb2cbf3d4ff15d
MD5 4dd231e247150b0978a93abcbc1e35df
BLAKE2b-256 ebc3d3a30afe24e972425402a6e9f6d245bc23b5fbdd9424e335c9ced3a52931

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for keysat_licensing_client-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 cc81ee229db98832a5ee2d2a207fb3587215544cafe3748c8ca04190ef7f9a64
MD5 f3b9d3033b4b44db62a3c62ec384e125
BLAKE2b-256 25ae5f98b6f94d7cbedd8b9574c62659a0140e0c1e47f29f2002a47186bf6171

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