Skip to main content

velsigil-client (Python)

Official Python client for the Velsigil license server. It validates license keys, binds them to the device, keeps an offline lease for when the server can't be reached, and checks for and downloads updates. Every response that can report success is Ed25519-signed by your Velsigil server and verified by the SDK before it is used.

  • Python 3.10+ on Windows, macOS and Linux (releases are tested on 3.10 and 3.14)
  • One runtime dependency: cryptography (≥ 50.0.0, the first release with no published advisory). PyNaCl (≥ 1.6.2) is an optional fallback.
  • HTTP uses the standard library (urllib), so there is no requests dependency

Contents

  1. Installation
  2. Quick start
  3. How it works
  4. API reference
  5. Result codes
  6. In-app free trials
  7. Offline leases
  8. Device secret persistence
  9. Updates and downloads
  10. Thread safety
  11. Hardening your integration
  12. Development and tests

Installation

pip install velsigil-client            # cryptography backend
pip install "velsigil-client[nacl]"    # also install the PyNaCl fallback backend

From a checkout of the SDK repository: pip install ./python.

Quick start

Get the API URL, product id and public key from the product's Integration tab in the Velsigil panel.

from velsigil_client import Code, FileStore, VelsigilClient, default_store_path

# Keep these as constants in your code (see "Hardening").
API_URL = "https://licenses.example.com"
PRODUCT_ID = "0b9f4c1e-8d6a-4f7e-9c3b-2a1d5e6f7a8b"
PUBLIC_KEY = "<your product's public key>"  # panel: Products > your product > Integration

client = VelsigilClient(
    API_URL,
    PRODUCT_ID,
    PUBLIC_KEY,
    store=FileStore(default_store_path("MyApp")),  # persists device secret + offline lease
)

result = client.validate_with_offline_fallback(license_key, version="1.2.0")
if not result.ok:
    show_error(result.code, result.message)        # e.g. "license_expired"
    raise SystemExit(1)

if result.has_feature("export"):
    enable_export()
print("Plan:", result.license.plan, "- days left:", result.days_remaining())

There is a complete, runnable program in examples/basic.py. Set the constants API_URL, PRODUCT_ID and PUBLIC_KEY at its top to your product's values (they ship as placeholders; until all three are set it prints a usage message and exits with code 2), then run:

python examples/basic.py --license-key VSG-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX
python examples/basic.py --license-key ... --download ./downloads/app.zip
python examples/basic.py --license-key ... --deactivate

The values are constants in code, as in your application, not read from the environment. For local testing only, the environment variables VELSIGIL_API_URL, VELSIGIL_PRODUCT_ID and VELSIGIL_PUBLIC_KEY override them when the API URL is loopback (localhost, 127.0.0.1, [::1], for example a panel dev server on http://localhost:3000); for any other server they are ignored.

How it works

Each request carries the product id, a fresh 32-byte random nonce (base64url, 43 characters) and a unix timestamp (local clock plus any offset learned from the server). Requests that involve a license also send the hardware id and, once issued, the device secret.

The server answers HTTP 200 with a signed envelope { data, sig, kid }. The SDK:

  1. verifies the Ed25519 signature over the exact ASCII bytes of data, using only the public key you passed to the constructor (the kid field is ignored, so the key can't be swapped);
  2. only then base64url-decodes and parses the JSON payload;
  3. rejects the payload unless it echoes this request's nonce, this product id and the expected response type. That stops replayed or redirected responses;
  4. returns a VelsigilResult. ok=True is only possible after all three steps succeed.

Unsigned HTTP errors (rate limiting, validation errors, blocked IPs, server errors) become failure results and can never produce ok=True. If the server reports a signed clock_skew, the SDK learns offset = serverTime - localTime and retries once with a new nonce.

Request methods never raise. Licensing problems and transport failures come back as a result with ok=False and a code. Exceptions are only raised by the constructor (bad configuration), by download_to_file (file errors) and by the low-level verification helpers.

API reference

VelsigilClient(api_url, product_id, public_key, *, ...)

Parameter Default Description
api_url — Server origin, e.g. https://licenses.example.com. /api/client/v1 is appended unless already present; a sub-path such as https://example.com/velsigil also works. Must be https://. Plain http:// is only accepted for localhost, 127.0.0.1 and ::1. URLs containing credentials, a query string or a fragment are rejected.
product_id — Product UUID (case-insensitive).
public_key — Product Ed25519 public key: standard base64 of the raw 32-byte key. Missing padding is tolerated. The public test keys of the SDK test vectors are refused unless api_url is a loopback host.
timeout 15.0 Seconds per request. Applies to connect and read, plus an overall deadline for the response body.
hwid auto Overrides the hardware id (8–256 printable characters). The default comes from get_hardware_id().
store MemoryStore() A LicenseStore that persists the device secret and offline lease per product. Use FileStore (or your own store) in production.
allow_insecure_http False Allows plain HTTP to non-local hosts. Never enable this in production.
ssl_context system CAs A custom ssl.SSLContext, for example one trusting a private CA. It must keep certificate and hostname verification on.
user_agent velsigil-client-python/<v> Overrides the User-Agent header.
clock time.time Callable that returns unix seconds. Useful for tests.
crypto_backend "auto" "auto" (cryptography, then PyNaCl), "cryptography" or "nacl".

The constructor raises ConfigurationError for invalid arguments, CryptoBackendError when no Ed25519 backend is installed, and HardwareIdError when the machine id can't be read and no hwid was given.

Methods

Method Description
validate(license_key, version=None, device_name=None) Validates the key and activates this device on first use. On success it stores the newly issued device secret and the offline lease. On a definitive signed denial (see Offline leases) it deletes the stored lease.
start_trial(version=None, device_name=None, email=None) Starts a free trial of the product on this device without a license key (see In-app free trials). On ok, result.trial_key is the new key: store it, then use validate(). Stores the device secret and lease like validate(). email (up to 254 characters) is sent only when given. Returns already_licensed locally (nothing sent, nothing changed) when a device secret or lease is already stored for the product, and store_unavailable when the store cannot be read.
deactivate(license_key) Releases this device's activation slot. On ok (or device_not_found) it clears the stored device secret and lease.
check_update(current_version=None) Returns result.update (UpdateInfo). The code is no_release when nothing is published.
get_download(license_key, version=None) Returns result.download (DownloadInfo) with a short-lived link. The device must already be activated when the product locks HWIDs.
download_to_file(download, destination) Streams the release to destination and checks its signed size and SHA-256 before moving it into place. Returns the absolute path; raises DownloadError on failure.
validate_offline() Checks the stored lease without network access. Result has offline=True.
validate_with_offline_fallback(license_key, version=None, device_name=None) Calls validate() first. Only if it returns network_error does it fall back to validate_offline().
clear_stored_state() Forgets the stored device secret and lease for this product.
VelsigilClient.get_hardware_id() (static) This machine's HWID (see below).

Properties: api_url (normalised), product_id, hwid, clock_offset (seconds learned from a signed clock_skew response), key_id (diagnostics only).

VelsigilResult

Member Description
ok: bool True only for a verified success.
code: str "ok" or a failure code (see Result codes).
message: str Server message for signed responses; fixed SDK text otherwise. Unsigned server text is never echoed.
license: LicenseInfo | None id, plan, status, features, expires_at, max_devices, devices_used, created_at, has_feature(name). On a failed result (license_revoked, license_expired, ...) license.features is informational only (servers since 2026-10-06 send it empty) and license.has_feature() is False; gate features with result.has_feature().
activation: ActivationInfo | None id, status, first_seen_at, device_secret_issued. The secret itself is never exposed.
lease: LeaseInfo | None token, expires_at.
update: UpdateInfo | None latest_version, min_version, update_available, mandatory, changelog.
download: DownloadInfo | None url (hidden from repr), expires_at, file_name, size, sha256, version.
request_id: str | None Server request id. Quote it in support requests.
offline: bool True when the result comes from the stored lease.
server_time, http_status, retry_after Diagnostics. retry_after is set for rate_limited.
trial_key: str | None The key of the trial start_trial() just started (ok results of start_trial only; hidden from repr). Sent once: store it immediately.
has_feature(name) True only if ok and the license includes name.
features Tuple of features; empty unless ok.
expires_at, expires_at_datetime, is_lifetime License expiry as unix seconds or an aware UTC datetime. None means lifetime or unknown.
is_trial True for a free-trial license (the optional signed trial field, online and offline; also license.is_trial). Pair it with days_remaining() for "Trial: N days left" and a "Buy now" link; a purchase with the same e-mail keeps the key and turns it False at the next online validation.
trial_ref The trial's conversion reference (servers since 2026-10-06; also license.trial_ref): set on online results for a free trial a purchase can still convert, also on license_expired; None otherwise and offline. Put it in your "Buy now" link with result.with_trial_ref(buy_url) (or velsigil_client.with_trial_ref(url, ref)): it appends velsigil_trial=<ref> (a Stripe Payment Link gets client_reference_id=), and the purchase then converts THIS trial (same key) whatever e-mail address the buyer pays with. Opaque; never store it.
seconds_remaining(now=None), days_remaining(now=None), is_expired(now=None) Expiry helpers; remaining time never goes below 0. seconds_remaining and is_expired default to the local clock. days_remaining rounds up and, without now, measures at the result's own time (the signed server_time online, the time of the check offline, kept in reference_time): an N-day trial shows N right after start_trial(), 1 on its last day and 0 once expired. The same rule in every Velsigil SDK.

All timestamps are unix seconds in UTC, exactly as signed by the server.

Stores

Class Description
LicenseStore Abstract interface: load(product_id) -> StoredState, save(product_id, state), delete(product_id). Implementations must be thread-safe.
MemoryStore() Keeps state in the process. It is lost on exit.
FileStore(path) JSON file holding every product. Atomic writes (temp file, then fsync, then os.replace). On POSIX the directory is 0700 and the file 0600. A corrupt file is treated as empty. Every FileStore on the same path in a process shares one lock, so several instances (for example one per product on default_store_path()) never lose each other's updates; sharing one instance is still simplest. Separate processes are not coordinated (see Thread safety).
default_store_path(app_name) Per-user path: %LOCALAPPDATA%\<app>\Velsigil\license.json on Windows, ~/Library/Application Support/<app>/Velsigil/license.json on macOS, $XDG_DATA_HOME/<app>/velsigil/license.json on Linux. Upgrading from an SDK released under the former product name (Veltrix): until the first write, a FileStore at this path reads the file left in the old Veltrix/veltrix directory, so installed apps keep their device secret and offline lease.
StoredState(device_secret=None, lease_token=None) Immutable value. Its repr hides the secret.

Hardware id

hwid = sha256_hex("vx-hwid-v1:" + machine_id.strip().lower()). The machine id comes from:

  • Windows: HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid, read from the 64-bit registry view
  • Linux: /etc/machine-id, then /var/lib/dbus/machine-id (a file that is empty or holds systemd's placeholder uninitialized is skipped)
  • macOS: IOPlatformUUID from /usr/sbin/ioreg -rd1 -c IOPlatformExpertDevice, run without a shell

hwid_from_machine_id(raw) exposes the derivation, and get_hardware_id() / velsigil_client.hwid.read_machine_id() expose the platform lookup. Every Velsigil SDK computes the same value. If you pass your own hwid, derive it from something stable; hashing it with hwid_from_machine_id is a good idea.

Low-level helpers

Ed25519Verifier(public_key), open_envelope(verifier, envelope, expected_nonce, expected_product_id, expected_type, expected_hwid=None), verify_lease(verifier, token, product_id, hwid, now) and key_id_for(public_key). These raise EnvelopeError / LeaseError, whose .reason is one of invalid_signature, nonce_mismatch, product_mismatch, type_mismatch, hwid_mismatch, expired or malformed. expected_type is required: pass the endpoint the request went to (validate, deactivate, update_check, download or trial). The server also signs ok: true answers to update_check, which needs no license, so the type must always be checked; None or an empty value raises ValueError. With expected_hwid (the hwid the request was sent with), a signed lease or activation.hwidHash bound to another device raises hwid_mismatch.

These helpers refuse the two public keys of the SDK test vectors (keys.publicKey and keys.wrongPublicKey in test-vectors.json, whose private keys are published there) on every host: they have no server URL, so unlike VelsigilClient they make no loopback exception. Ed25519Verifier, key_id_for and velsigil_client.crypto.decode_public_key refuse the key itself (in any accepted spelling), open_envelope and verify_lease a verifier that holds it, before anything else is checked. The refusal is a ConfigurationError (code invalid_configuration, the error of an invalid key) with the same message as the client constructor's. There is no opt-out argument; the SDK's own test-vector suite uses internal, unguarded equivalents.

Exceptions

VelsigilError is the base class of ConfigurationError (also a ValueError), CryptoBackendError, HardwareIdError, StoreError, DownloadError, EnvelopeError and LeaseError.

ConfigurationError.code is "invalid_configuration" and DownloadError.code is one of download_failed (non-200 status, e.g. an expired link answering 410, or a URL rejected by the HTTPS policy), integrity_mismatch (size or SHA-256 differs from the signed values; the partial file is deleted), io_error (the destination could not be written), network_error (transport failure) or validation_error (not a DownloadInfo). These are the cross-SDK codes of SPEC section 14.

Result codes

Code Origin Meaning / suggested handling
ok signed Success.
invalid_key signed Key not found for this product.
license_expired · license_suspended · license_revoked · license_banned signed License state does not allow use. Show the message; renewal or support.
device_limit_reached signed All device slots in use. The customer can free one in the portal.
device_revoked signed This device was revoked by the seller.
device_verification_failed signed Device secret mismatch on a strict product. Reset devices in the portal.
device_not_found signed deactivate: this device is not registered.
device_not_activated signed get_download: validate (activate) first.
activation_rate_limited · activation_cooldown · activations_disabled signed New activations are temporarily not possible.
downloads_disabled · release_not_found · no_release signed Download or update not available.
blacklisted signed Device, IP or customer is blocked.
outdated_version signed Version below the product minimum. result.update tells you what to install.
product_paused · product_disabled signed Product unavailable (the message is set by the seller).
clock_skew signed Clock still too far off after the automatic retry.
replay_detected signed Nonce reuse detected by the server (should not happen with this SDK).
trial_already_used signed This device already used a free trial of the product. Offer to buy; a stored lease is kept.
trial_unavailable signed start_trial: the seller offers no in-app trial right now (switched off, today's limit, too many trials from this network). Show the message.
trial_email_required · trial_email_invalid · trial_email_not_accepted signed start_trial: the offer confirms an e-mail address first: ask for one, a valid one, or a personal / work address (throwaway domains are refused).
trial_confirmation_sent signed start_trial: not an error of the user. A confirmation link was e-mailed; the key arrives by e-mail and is entered like any key. The same answer for every valid address.
validation_error unsigned 400 Request rejected. Also returned locally, without a request, for an empty or oversized key, version or device name.
ip_blocked unsigned 403 Network temporarily blocked.
unknown_product unsigned 404 Wrong product id or server.
rate_limited unsigned 429 Back off. result.retry_after holds seconds when sent.
internal_error unsigned 5xx Server error (also 502/503/504 with a Velsigil error body).
payload_too_large · unsupported_media_type unsigned 413/415 Should not happen with this SDK (also mapped from a bodiless 413/415, e.g. from a proxy).
network_error SDK DNS, connect, TLS or timeout failure, or an HTTP 502/503/504 without a Velsigil error body (gateway can't reach the server). This is the only code that triggers the offline fallback.
invalid_response SDK Signature invalid, nonce/product/type mismatch (the signed type must match the endpoint), a signed lease or activation.hwidHash that belongs to another device (the request was rewritten in transit; nothing from the response is stored), malformed or oversized body, unsigned success, unexpected status (including redirects, which are never followed). Treat it as a failure.
no_lease · lease_expired · lease_invalid SDK Offline validation: nothing stored, lease past exp, or lease rejected (signature, type, product or HWID).
invalid_configuration SDK ConfigurationError.code (the constructor raises).
download_failed · integrity_mismatch · io_error SDK DownloadError.code from download_to_file().
panel_too_old SDK start_trial: the Velsigil server has no in-app trial endpoint yet (HTTP 404 with the Velsigil error code not_found). The seller must update the panel.
already_licensed SDK start_trial: this device already holds a license for the product (a device secret or offline lease is stored), and a trial must not replace it. Nothing was sent and the stored state is unchanged. Validate the saved key instead, or call deactivate(key) / clear_stored_state() first.
store_unavailable SDK start_trial: the store could not be read (for example a locked file or keyring), so the SDK cannot tell whether this device already holds a license (Code.STORE_UNAVAILABLE). Nothing was sent; try again later.

The constants live in velsigil_client.Code (for example Code.LICENSE_EXPIRED).

Unsigned errors are mapped the same way in every Velsigil SDK: a known code in a Velsigil error body ({"error": {"code": ...}}) wins; otherwise 400 → validation_error, 413 → payload_too_large, 415 → unsupported_media_type, 429 → rate_limited, 502/503/504 → network_error without a Velsigil error body or internal_error with one, other 5xx → internal_error, anything else → invalid_response. The unsigned message text is never shown.

In-app free trials

When the seller turns on In your app in the product's trial offer (panel → Products → your product → Trials), an app that has no license key yet can start a free trial for its device:

saved_key = load_saved_license_key()  # your own settings storage
if saved_key is None:
    trial = client.start_trial(version="1.2.0", device_name=socket.gethostname())
    if trial.ok and trial.trial_key:
        save_license_key(trial.trial_key)  # FIRST: the server can never send this key again
        show_trial_banner(trial.days_remaining())  # trial.is_trial is True
    elif trial.code == Code.TRIAL_EMAIL_REQUIRED:
        pass  # the offer confirms an e-mail address first: ask for it, then start_trial(email=...)
    else:
        show_message(trial.message)  # trial_confirmation_sent, trial_already_used, trial_unavailable, panel_too_old, ...
  • One trial per device and product. A device that already had a trial of the product (from the app, the seller's website or the customer portal) gets trial_already_used; offer to buy.
  • The trial key is an ordinary license key on the seller's trial plan. Store it like a key the user typed and use validate() from then on. Show it in your About / License screen: the customer needs it for the customer portal, to move the trial to another device and for support. A purchase converts the same key (the seller's staff, or a purchase with the e-mail address the trial was confirmed with).
  • E-mail confirmation (optional, the seller's choice). Then the first answer is trial_confirmation_sent; the customer confirms the address from the e-mail, gets the key on the confirmation page and by e-mail, and enters it in the app (validate()).
  • The SDK stores the trial's device secret and offline lease like after a validation, never the key.
  • Never over an existing license. When a device secret or an offline lease is already stored for the product (the device was activated with a key, paid or trial), start_trial() returns already_licensed (Code.ALREADY_LICENSED) without sending anything and leaves the stored state as it is, so a trial can never replace the device secret and lease of a paid license. Validate the saved key instead; to really start over, call deactivate(key) (or clear_stored_state() when the key is gone) first. When the store cannot be read, start_trial() returns store_unavailable and sends nothing: a failed read is never taken for "nothing stored".
  • The server limits trials per network, per device and per day, and HWIDs are asserted by the client: a user who changes the machine id can start another trial within those limits. Treat a trial as a marketing tool, not a security boundary.
  • panel_too_old: the seller's Velsigil server predates in-app trials; nothing else is affected.

Offline leases

When the product's offline lease hours is above 0, a successful validate() (or start_trial()) returns a signed lease base64url(JSON).base64url(signature) bound to the product, this device's HWID hash and an expiry exp = min(now + offlineLeaseHours, licenseExpiresAt) (plus trial: true for a free-trial license; fields the SDK does not know are ignored). The SDK verifies the lease and then stores it. A validate() answer whose lease is bound to another device or product is rejected as a whole (invalid_response), not accepted without the lease.

  • validate_offline() accepts the stored lease only if the signature is valid, typ is "lease", product id and HWID hash match, and now < exp.
  • validate_with_offline_fallback() uses the lease only for network_error. A business failure, rate limiting or an invalid or unsigned response never falls back, because an attacker who can tamper with responses must not be able to switch the app to offline mode.
  • A signed, definitive denial deletes the stored lease. The set is the same in every Velsigil SDK (velsigil_client.LEASE_REVOKING_CODES): invalid_key, license_expired, license_suspended, license_revoked, license_banned, device_revoked, device_verification_failed, device_limit_reached, device_not_activated, device_not_found, blacklisted, product_disabled. A success without a lease (offline leases disabled) and a successful deactivate() also clear it. Any other signed failure (product_paused, outdated_version, activation_rate_limited, clock_skew, replay_detected, ...) leaves it alone.
  • Known limitation: offline validation trusts the local clock (plus any offset learned online). Someone who winds the clock back can extend offline use up to the lease's own expiry. Keep lease hours short.

Device secret persistence

On the first activation the server issues a device secret (activation.deviceSecret). The SDK stores it in the configured store, keyed by product id, and sends it with every later validate, deactivate and get_download call. The server keeps only a hash of it.

  • Use a persistent store. With the default MemoryStore the secret is lost on restart. The server then counts a secret mismatch, and products with strict device binding refuse the device (device_verification_failed) until the customer resets their devices.
  • FileStore writes atomically with owner-only permissions on POSIX. On Windows, default_store_path() places the file under %LOCALAPPDATA%, which only the user (and administrators) can read. Implement LicenseStore to keep it in the OS keychain or your own encrypted settings.
  • If saving fails, the SDK logs a warning and keeps the state in memory for the rest of the process.
  • If loading fails, the SDK logs a warning and keeps working from the last state it read or wrote, but never treats the failed read as "nothing stored": start_trial() refuses with store_unavailable, and an answer is merged into a fresh read of the store. While the store stays unreadable nothing is written except a device secret the server just issued.
  • deactivate() (success) and clear_stored_state() remove the secret.

Updates and downloads

update = client.check_update("1.2.0").update
if update and update.update_available:
    link = client.get_download(license_key, update.latest_version)
    if link.ok:
        path = client.download_to_file(link.download, "downloads/app.zip")  # raises DownloadError

get_download returns a signed link that expires within minutes, plus the file's signed size and sha256. download_to_file streams to a temporary file next to the destination. It aborts once the download exceeds the signed size, compares the SHA-256 in constant time, and only then renames the file into place. A tampered or truncated download therefore never appears at the destination path. Links must be https:// (or local http://); redirects are not followed. update.mandatory and outdated_version tell you when to block the old version.

Thread safety

A VelsigilClient can be shared between threads:

  • validate, start_trial, deactivate, get_download and clear_stored_state are serialised per client, because they read and update the device secret and lease. This also guarantees that concurrent first activations don't race for the device secret, and that the already_licensed check of start_trial and its request form one step.
  • check_update and validate_offline run concurrently.
  • The clock offset and cached state are protected by a lock. MemoryStore and FileStore are thread-safe; FileStore objects on the same path share one lock per process.
  • Use one VelsigilClient per product and, ideally, one FileStore per file. Separate processes sharing one FileStore file are not coordinated: each write replaces the whole file, which holds every product, so a write for one product can drop another product's concurrent update. Let one process own the file, or give each product its own file (FileStore(default_store_path("MyApp", "license-<product>.json"))).

Hardening your integration

Client-side licensing raises the cost of piracy; it can't make it impossible. Plan with that in mind:

  • Keep the public key in code as a constant. Don't load it from a config file, an environment variable or the network, or a user can swap in their own key and sign their own "valid" responses. Rotating the signing key in the panel requires shipping a new build.
  • Check results in several places, not with one if at start-up that a patch can remove. Re-check result.ok and has_feature() where features are used, and re-validate periodically (for example every few hours or on important actions).
  • Server-side enforcement is authoritative. Revocation, device limits, expiry and downloads are enforced by the server. Anything valuable (content, cloud features, updates) should be delivered only after a successful server check, ideally gated by the server.
  • Obfuscation helps but is not a security boundary. Python bytecode is easy to inspect, so packers and obfuscators only slow an attacker down.
  • Never disable HTTPS (allow_insecure_http) or certificate verification in production builds.
  • Treat every ok=False, and especially invalid_response, as "not licensed". Don't add a "fail open" path.
  • The SDK never logs license keys, device secrets, lease tokens or download URLs, and the repr of its objects hides them. Keep it that way in your own logging: show users at most the last 5 characters of a key.

Development and tests

In the SDK repository, from this folder:

python -m pip install "cryptography>=50.0.0"
python -m unittest discover -s tests -t . -v
  • tests/test_vectors.py classifies every envelope, lease and HWID vector in the shared test-vectors.json (one folder up) with each installed backend. It also proves that the mock server encodes and signs byte-for-byte like the real server. The private keys of the vectors are published there, so VelsigilClient refuses their two public keys (ConfigurationError) unless api_url is a loopback host (localhost, 127.0.0.1 or ::1), and the public low-level helpers refuse them on every host; the suite verifies the vectors through the SDK's internal, unguarded path and checks both refusals.
  • tests/test_client.py drives the client against tests/mock_server.py, a threaded local HTTP server that signs with the vector key. It covers success, business failure, nonce, product and type mismatch, bad signatures, unsigned success, clock skew with a single retry, device secret persistence and re-sending, 400/403/404/429/500/502/503, redirects, oversized bodies, timeouts, connection refused, offline fallback with a fake clock, deactivation, updates, verified downloads, concurrency and log hygiene.

Set VELSIGIL_TEST_VECTORS to use a vectors file at another location.

Before a release this SDK is also run against a real Velsigil server (activation, device-secret persistence, offline lease, updates, verified download, clock skew, replay, suspension, invalid key, deactivation).

Metadata

Release files for velsigil-client 1.0.2

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

Source distribution (sdist)

Source distribution for velsigil-client 1.0.2
File Size Uploaded
velsigil_client-1.0.2.tar.gz 92.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for velsigil-client 1.0.2
File Interpreter ABI Platform
velsigil_client-1.0.2-py3-none-any.whl Python 3 none any Details

Total release size: 143.5 kB

Release files / velsigil_client-1.0.2.tar.gz

Download URL velsigil_client-1.0.2.tar.gz
Size 92.8 kB
Tags Source
SHA-256 checksum
How to use checksums
e973bc07e38090cacab8e982f02fde8d9b281ac187671719d9d8426d9a9581b1
BLAKE2b-256 checksum
How to use checksums
ff29bbf430fe27fc6ef0892b719735e7c783ab189ede37b9df6626bd3ec41be2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.

Transparency log

Release files / velsigil_client-1.0.2-py3-none-any.whl

Download URL velsigil_client-1.0.2-py3-none-any.whl
Size 50.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
09a2c65c0397f93252d15bb0a1d2bab1a4d694f08cc6bb735448d3993b4cae06
BLAKE2b-256 checksum
How to use checksums
ef20519b950e3efe48a967b28e6c41008a0c46432de0db6853ee66a3f4fef2d0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.

Transparency log

Release history Release notifications | RSS feed

1.0.4

2 release files

1.0.3

2 release files

This release

1.0.2 This release

2 release files

1.0.1

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