Skip to content

Licensing System

This topic explains how PyLocket's licensing and activation system works, including activation flow, device management, offline support, and license lifecycle.


Architecture

sequenceDiagram
    participant A as End-User App
    participant P as PyLocket API
    participant D as Developer Portal

    A->>P: 1. Activation request (key + device ID)
    Note over P: 2. Validate license<br/>Check device limit
    P->>A: 3. Signed token
    Note over A: 4. Cache token<br/>Decrypt functions<br/>App runs
    A->>P: 5. Periodic refresh
    P->>A: Refreshed token

License Key Format

XXXX-XXXX-XXXX-XXXX
  • Length: 16 alphanumeric characters in 4 groups of 4
  • Character set: Uppercase letters and digits (ambiguous characters excluded: 0/O, 1/I/L)
  • Uniqueness: Globally unique across all PyLocket apps
  • Platform-independent: A single key works on all platforms for the same app

Example

A7K2-M9P4-X3J8-W5N6

Device Fingerprinting

When an end-user activates a license, the application generates a one-way device fingerprint based on hardware characteristics.

Key Properties

  • Per-app isolation: The same physical device produces different fingerprints for different apps, preventing cross-app tracking
  • Privacy-preserving: Only a one-way device fingerprint is transmitted to PyLocket servers — raw hardware identifiers never leave the device
  • Deterministic: The same device always produces the same fingerprint for the same app, allowing seamless re-activation after restart
  • Non-reversible: The stored fingerprint cannot be reversed to reveal hardware details

Activation Flow

Step 1: Activation Request

When the end-user launches the protected application and enters their license key, the bootstrap sends an activation request to the PyLocket activation service with the license key and a one-way device fingerprint.

Step 2: Server Validation

The activation service validates:

  1. License exists: The key matches a valid license record
  2. License is active: Status is ACTIVE (not REVOKED or EXPIRED)
  3. Device limit check: The number of registered devices does not exceed the limit
  4. Rate limiting: Activation requests are rate-limited to prevent abuse
  5. Build check: The build exists and belongs to the correct app
  6. Key management: The encrypted key material for the build is decrypted server-side using cloud-based key management. The resulting material is re-encrypted for the specific activation before being returned.

Step 3: Token Issuance

On success, the activation service returns a cryptographically signed runtime token bound to the device, app, and build, along with encrypted key material and a policy containing configurable token expiry and offline grace period.

Step 4: Caching

The activation response is cached locally. Subsequent application launches check this cache first, avoiding a network round-trip if the cached token is still valid.

Step 5: Runtime Initialization

The bootstrap initializes the native runtime with the received key material. The native runtime:

  1. Validates the token and verifies the manifest signature
  2. Uses the key material to enable on-demand function decryption
  3. Begins decrypting functions as they are called
  4. Periodically refreshes the token in the background

Offline Support

Online Activation

The first activation always requires an internet connection. After successful activation:

  1. The runtime token is cached locally
  2. The token has a configurable expiry

Offline Grace Period

If the device loses internet connectivity:

  1. The cached token remains valid until its expiry
  2. After expiry, the runtime enters the offline grace period (configurable by the developer)
  3. During the grace period, the application continues to function
  4. After the grace period expires, the application requires re-activation
flowchart LR
    A["Online activation"] --> B["Token cached"]
    B --> C["Grace period"]
    C --> D["Re-activation required"]

Background Refresh

When the device is online, the runtime attempts to refresh the token:

  1. Before token expiry (proactive refresh)
  2. On application startup
  3. Periodically during execution

A successful refresh resets both the token TTL and the offline grace timer.


Device Limits

How Limits Work

Each license has a device_limit (default: 1, configurable up to 10,000). When a new device activates:

  • If active_devices < device_limit: Activation succeeds, new device registered
  • If active_devices >= device_limit: Activation fails with DEVICE_LIMIT_EXCEEDED

Counting Devices

A "device" is identified by its unique device identifier. The same physical device always produces the same identifier, so:

  • Restarting the application on the same device does not consume an additional slot
  • Reinstalling the application on the same device produces the same identifier (unless the OS is reinstalled or hardware changes)
  • Running on a VM that migrates to different hardware may produce a different identifier

Resetting Devices

Developers can reset device activations when customers replace hardware:

pylocket licenses reset-devices --license <LICENSE_KEY>

This clears all device records, allowing the customer to activate fresh.


Velocity Controls

Activation Rate Limiting

Activation requests are rate-limited per license key to prevent: - Automated license key sharing/testing - Brute-force activation attempts - Denial-of-service against the activation endpoint

When the rate limit is exceeded, the API returns 429 Too Many Requests with a Retry-After header.

Download Rate Limiting

Download requests are also rate-limited to prevent abuse of signed download URLs.


License Lifecycle

flowchart TD
    CREATE["CREATE"] --> ACTIVE["Status: ACTIVE"]
    ACTIVE --> ACT1["Customer activates → Device registered"]
    ACTIVE --> ACT2["Customer activates on new device → Second device registered"]
    ACTIVE --> EXTEND["Developer extends expiry → Expiry updated"]
    ACTIVE --> REVOKE["Developer revokes"]
    REVOKE --> REVOKED["Status: REVOKED → Activation fails on next check"]
    ACTIVE --> EXPIRY["Expiry date passes"]
    EXPIRY --> EXPIRED["Status: EXPIRED → Activation fails on next check"]
    ACTIVE --> RESET["Developer resets devices → All device records cleared"]

See Also