Skip to main content

Python client library for the Labs64 NetLicensing RESTful API

Project description

Innovative License Management Solution

Labs64 NetLicensing Client (Python)

Python Client - CI PyPI PyPI - Downloads PyVer codecov
Apache License 2.0 📖 Documentation NetLicensing @ LinkedIn

Python client library for the Labs64 NetLicensing RESTful API.

Built on Python 3.11+, httpx and Pydantic v2. Supports API-key and username/password authentication, automatic retries with exponential back-off, strongly-typed response models, and transparent pagination.


Table of Contents


Installation

pip install netlicensing-client

For local development (installs test tools, type checker, and build utilities):

git clone https://github.com/Labs64/NetLicensingClient-python.git
cd NetLicensingClient-python
pip install -e ".[dev]"

Quickstart

from netlicensing import NetLicensingClient

# Authenticate with an API key (or set NETLICENSING_API_KEY in the environment)
client = NetLicensingClient(api_key="YOUR_API_KEY")

result = client.validation.validate(
    "CUSTOMER-1",
    product_number="PRODUCT-1",
    product_module_parameters={
        "MODULE-1": {"nodeSecret": "machine-or-device-id"},
    },
)

if result.is_valid():
    print("✅ License is valid")
else:
    print("❌ License is invalid or expired")

Configuration and Authentication

Constructor parameters take precedence over environment variables, which take precedence over defaults.

Environment variables

Variable Default Description
NETLICENSING_API_KEY API key (recommended)
NETLICENSING_USERNAME Username for basic auth
NETLICENSING_PASSWORD Password for basic auth
NETLICENSING_VENDOR_NUMBER Vendor number (informational)
NETLICENSING_BASE_URL https://go.netlicensing.io/core/v2/rest API base URL
NETLICENSING_TIMEOUT 30.0 Total request timeout in seconds
NETLICENSING_CONNECT_TIMEOUT 10.0 Connection timeout in seconds
NETLICENSING_RETRIES 2 Maximum retry attempts on transient errors
NETLICENSING_RETRY_BACKOFF 0.5 Base back-off in seconds (doubles per attempt)
NETLICENSING_VERIFY true Verify TLS certificates
export NETLICENSING_API_KEY="YOUR_API_KEY"
export NETLICENSING_VENDOR_NUMBER="VXXXXXXXX"
from netlicensing import NetLicensingClient

# No arguments needed — credentials are read from the environment
client = NetLicensingClient()

API-key authentication (recommended)

NetLicensing uses HTTP Basic Auth with username apiKey and the key as the password. Never hard-code API keys — use environment variables, deployment secrets, or a secrets manager.

client = NetLicensingClient(api_key="YOUR_API_KEY")

Username / password authentication

Suitable for trusted server-side contexts only.

client = NetLicensingClient(username="demo", password="demo")

Advanced constructor parameters

client = NetLicensingClient(
    api_key="YOUR_API_KEY",
    timeout=60.0,
    connect_timeout=15.0,
    retries=3,
    retry_backoff=1.0,
    headers={"X-Custom-Header": "value"},
)

Services

The client exposes one service attribute per NetLicensing resource:

Attribute Resource Main operations
client.products Product create, get, list, iterate, update, delete
client.product_modules ProductModule create, get, list, iterate, update, delete
client.licensees Licensee create, get, list, iterate, update, delete, validate, transfer
client.license_templates LicenseTemplate create, get, list, iterate, update, delete
client.licenses License create, get, list, iterate, update, delete
client.tokens Token create, get, list, iterate, delete, create_shop_token, create_api_key_token
client.transactions Transaction get, list, iterate
client.payment_methods PaymentMethod get, list, iterate
client.bundles Bundle create, get, list, iterate, update, delete, obtain
client.notifications Notification create, get, list, iterate, update, delete
client.utility (reference data) list_countries, list_license_types, list_licensing_models
client.validation (shortcut) validate

Products

from netlicensing import NetLicensingClient

client = NetLicensingClient()

# Create
product = client.products.create(
    number="P-MYAPP-1",
    name="My Application",
    version="2.0",
    active=True,
    description="A great application",
    licensee_auto_create=True,
)

# Read
product = client.products.get("P-MYAPP-1")
print(product.name, product.version)

# List (first page)
page = client.products.list({"active": True})
for p in page:
    print(p.number, p.name)

# Update
product = client.products.update("P-MYAPP-1", name="My Application v2", version="2.1")

# Delete (cascade removes all dependent resources)
client.products.delete("P-MYAPP-1", force_cascade=True)

Product Modules

from netlicensing import LicensingModel

module = client.product_modules.create(
    product_number="P-MYAPP-1",
    number="M-MAIN",
    name="Main Module",
    licensing_model=LicensingModel.SUBSCRIPTION,
    active=True,
)

module = client.product_modules.get("M-MAIN")
client.product_modules.update("M-MAIN", name="Main Module (updated)")
client.product_modules.delete("M-MAIN")

Licensees

# Create a customer
licensee = client.licensees.create(
    product_number="P-MYAPP-1",
    number="CUSTOMER-42",
    name="Acme Corp",
    active=True,
)

# Read
licensee = client.licensees.get("CUSTOMER-42")

# Update
client.licensees.update("CUSTOMER-42", name="Acme Corporation", active=True)

# Transfer all licenses to another licensee
client.licensees.transfer("CUSTOMER-42", source_licensee_number="CUSTOMER-OLD")

# Delete
client.licensees.delete("CUSTOMER-42")

Validation

Use client.validation.validate() (or client.licensees.validate()) to check whether a customer holds a valid license.

from netlicensing import ValidationParameters

# Simple call — keyword arguments
result = client.validation.validate(
    "CUSTOMER-42",
    product_number="P-MYAPP-1",
    product_module_parameters={
        "M-FLOATING": {"sessionId": "session-abc123", "action": "checkOut"},
    },
)

# Using a ValidationParameters model
params = ValidationParameters(
    product_number="P-MYAPP-1",
    product_module_parameters={
        "M-NODELOCKED": {"nodeSecret": "hardware-fingerprint"},
    },
)
result = client.validation.validate("CUSTOMER-42", params)

# Inspect the result
print(result.is_valid())          # True if all modules are valid
print(result.ttl)                  # TTL in seconds (for offline use)

module_result = result.by_product_module("M-MAIN")
if module_result:
    print(module_result.valid, module_result.licensing_model, module_result.warning_level)

License Templates

from netlicensing import LicenseType

# FEATURE template (perpetual, free)
template = client.license_templates.create(
    product_module_number="M-MAIN",
    number="LT-FEATURE",
    name="Feature License",
    license_type=LicenseType.FEATURE,
    price=0,
    active=True,
    automatic=True,   # auto-assign to new licensees
)

# TIMEVOLUME template (annual subscription, € 99)
template = client.license_templates.create(
    product_module_number="M-SUB",
    number="LT-ANNUAL",
    name="Annual Subscription",
    license_type=LicenseType.TIMEVOLUME,
    price=99.00,
    currency="EUR",
    timeVolume=365,
    timeVolumePeriod="DAY",
)

template = client.license_templates.get("LT-ANNUAL")
client.license_templates.update("LT-ANNUAL", price=79.00)
client.license_templates.delete("LT-ANNUAL")

Licenses

from datetime import date

# Assign a license to a customer
license_obj = client.licenses.create(
    licensee_number="CUSTOMER-42",
    license_template_number="LT-ANNUAL",
    active=True,
    startDate=date.today().isoformat(),
)

license_obj = client.licenses.get(license_obj.number)
client.licenses.update(license_obj.number, active=False)
client.licenses.delete(license_obj.number)

Tokens

NetLicensing Shop token

Generates a one-time checkout URL for the customer-facing NetLicensing Shop.

token = client.tokens.create_shop_token(
    "CUSTOMER-42",
    product_number="P-MYAPP-1",
    success_url="https://vendor.example/success",
    cancel_url="https://vendor.example/cancel",
    success_url_title="Back to the app",
    cancel_url_title="Cancel",
)

print(token.shop_url)   # redirect the customer here

API key token

from netlicensing import ApiKeyRole

token = client.tokens.create_api_key_token(
    api_key_role=ApiKeyRole.OPERATION,
)

print(token.number)   # use as the api_key for a scoped client

Token management

# List all active tokens
page = client.tokens.list()

# Revoke a token
client.tokens.delete("TOKEN-NUMBER")

Bundles

A bundle groups one or more license templates so they can be sold together.

# Create a bundle
bundle = client.bundles.create(
    number="B-STARTER",
    name="Starter Pack",
    license_template_numbers=["LT-FEATURE-A", "LT-FEATURE-B"],
    price=49.00,
    currency="EUR",
    active=True,
)

bundle = client.bundles.get("B-STARTER")
client.bundles.update("B-STARTER", name="Starter Pack (updated)")

# Obtain — creates licenses from all templates in the bundle for a customer
licenses_page = client.bundles.obtain("B-STARTER", licensee_number="CUSTOMER-42")

client.bundles.delete("B-STARTER")

Transactions

Transactions are created automatically by NetLicensing.

# List closed transactions
page = client.transactions.list({"status": "CLOSED"})
for txn in page:
    print(txn.number, txn.status, txn.grand_total, txn.currency)

# Retrieve a single transaction
txn = client.transactions.get("TX-12345")

Payment Methods

page = client.payment_methods.list({"active": True})
for pm in page:
    print(pm.number)

Notifications

from netlicensing import NotificationProtocol, NotificationEvent

notification = client.notifications.create(
    number="NOTIF-1",
    name="License Events Webhook",
    protocol=NotificationProtocol.WEBHOOK,
    endpoint="https://vendor.example/hooks/netlicensing",
    events=[NotificationEvent.LICENSE_CREATED, NotificationEvent.LICENSE_UPDATED],
    active=True,
)

client.notifications.update("NOTIF-1", name="License Events Webhook (updated)")
client.notifications.delete("NOTIF-1")

Utility

# Countries with VAT rates
page = client.utility.list_countries()
for country in page:
    print(country.code, country.name, country.vat_percent, country.is_eu)

# Supported license types and licensing models
print(client.utility.list_license_types())
print(client.utility.list_licensing_models())

Pagination and Iteration

All list() calls return a Page[T] object:

page = client.licensees.list({"active": True})

print(page.page_number)   # 0-based current page
print(page.total_pages)
print(page.total_items)
print(page.has_next)

for licensee in page:     # Page is iterable
    print(licensee.number)

print(len(page))          # number of items on this page
print(bool(page))         # False when the page is empty

Use iterate() to transparently walk all pages without manual pagination:

for licensee in client.licensees.iterate({"active": True}):
    print(licensee.number, licensee.name)

Control pagination explicitly:

page = client.licensees.list(pageNumber=0, itemsNumber=20)
while page.has_next:
    page = client.licensees.list(pageNumber=page.page_number + 1, itemsNumber=20)
    for item in page:
        print(item.number)

Error Handling

All exceptions inherit from NetLicensingError:

NetLicensingError
├── NetLicensingHTTPError       status_code, payload, method, url, request_id
│   └── NetLicensingAuthError   raised on HTTP 401 / 403
├── NetLicensingNetworkError    raised on connection or protocol errors
│   └── NetLicensingTimeoutError  raised when the request times out
└── NetLicensingValidationError raised when a response cannot be parsed
from netlicensing import (
    NetLicensingAuthError,
    NetLicensingHTTPError,
    NetLicensingNetworkError,
    NetLicensingTimeoutError,
    NetLicensingValidationError,
)

try:
    product = client.products.get("UNKNOWN")
except NetLicensingAuthError:
    print("Check your API key or permissions")
    raise
except NetLicensingHTTPError as exc:
    print(f"HTTP {exc.status_code}: {exc}")
    print(f"Request: {exc.method} {exc.url}")
    if exc.request_id:
        print(f"Request-ID: {exc.request_id}")
except NetLicensingTimeoutError:
    print("Request timed out — retry later")
except NetLicensingNetworkError:
    print("Network error")
except NetLicensingValidationError:
    print("Unexpected response format")

Context Manager

NetLicensingClient implements the context manager protocol. The underlying HTTP connection pool is released on exit.

with NetLicensingClient(api_key="YOUR_API_KEY") as client:
    result = client.validation.validate("CUSTOMER-42")
    print(result.is_valid())
# HTTP client is closed automatically

Models and Enums

All public symbols are importable directly from netlicensing:

from netlicensing import (
    # Client
    NetLicensingClient,
    NetLicensingConfig,

    # Exceptions
    NetLicensingError,
    NetLicensingAuthError,
    NetLicensingHTTPError,
    NetLicensingNetworkError,
    NetLicensingTimeoutError,
    NetLicensingValidationError,

    # Entity models
    Bundle,
    Country,
    License,
    LicenseTemplate,
    Licensee,
    Notification,
    Page,
    PaymentMethod,
    Product,
    ProductDiscount,
    ProductModule,
    Token,
    Transaction,
    ValidationParameters,
    ValidationResult,

    # Enums
    ApiKeyRole,
    LicenseeSecretMode,
    LicenseType,
    LicensingModel,
    NotificationEvent,
    NotificationProtocol,
    TokenType,
    TransactionSource,
    TransactionStatus,
)

Key enums

Enum Values
LicensingModel TRY_AND_BUY, SUBSCRIPTION, RENTAL, FLOATING, MULTI_FEATURE, PAY_PER_USE, PRICING_TABLE, QUOTA, NODE_LOCKED, DISCOUNT
LicenseType FEATURE, TIMEVOLUME, FLOATING, QUANTITY
TokenType DEFAULT, SHOP, APIKEY, ACTION
ApiKeyRole LICENSEE, ANALYTICS, OPERATION, MAINTENANCE, ADMIN
TransactionStatus PENDING, CLOSED, CANCELLED
NotificationProtocol EMAIL, WEBHOOK
NotificationEvent TRANSACTION_STATUS_CHANGED, LICENSEE_CREATED, LICENSEE_UPDATED, LICENSE_CREATED, LICENSE_UPDATED

Custom Properties

NetLicensing supports arbitrary custom properties on most resources. Pass them as keyword arguments or include them in a model instance:

# As keyword arguments
licensee = client.licensees.create(
    product_number="P-MYAPP-1",
    number="CUSTOMER-99",
    name="Widget Corp",
    companyId="WIDGET-CORP",   # custom property
    tier="enterprise",         # custom property
)

# Via model (extra fields are preserved and round-tripped)
from netlicensing import Licensee

licensee = Licensee(number="CUSTOMER-99", name="Widget Corp", companyId="WIDGET-CORP")
result = client.licensees.update("CUSTOMER-99", licensee)

# Access custom properties
print(licensee.model_extra)   # {'companyId': 'WIDGET-CORP', 'tier': 'enterprise'}

Demo

A small command-line demo app is included in demo/.

# Validate a licensee
NETLICENSING_API_KEY=YOUR_KEY python demo/app.py validate CUSTOMER-42 \
    --product-number P-MYAPP-1 \
    --module M-MAIN \
    --node-secret "my-hardware-id"

# Generate a Shop checkout URL
NETLICENSING_API_KEY=YOUR_KEY python demo/app.py shop-token CUSTOMER-42 \
    --product-number P-MYAPP-1 \
    --success-url https://vendor.example/success \
    --cancel-url https://vendor.example/cancel

Development

# Run the full test suite (no network access needed — uses httpx.MockTransport)
pytest

# Build distribution packages
python -m build

# Validate the built packages before uploading
twine check dist/*

# Type-check the library
mypy

The test suite does not call live NetLicensing servers. To run the demo-account smoke test when you have internet access:

NETLICENSING_LIVE_DEMO=1 pytest tests/test_live_demo.py -v

That test authenticates as demo:demo, creates a temporary APIKEY token for the demo vendor, uses it for a product-list call, and revokes the token.


References


How to Contribute

Everyone is welcome to contribute to this project! Once you're done with your changes, send a pull request and check CI Status. Thanks!

Bugs and Feedback

For bugs, questions, and discussions please use GitHub Issues.

License

This library is open-sourced software licensed under the Apache License Version 2.0.


Visit Labs64 NetLicensing at https://netlicensing.io

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

netlicensing_client-0.1.0.tar.gz (41.3 kB view details)

Uploaded Source

Built Distribution

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

netlicensing_client-0.1.0-py3-none-any.whl (35.2 kB view details)

Uploaded Python 3

File details

Details for the file netlicensing_client-0.1.0.tar.gz.

File metadata

  • Download URL: netlicensing_client-0.1.0.tar.gz
  • Upload date:
  • Size: 41.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for netlicensing_client-0.1.0.tar.gz
Algorithm Hash digest
SHA256 bdea5c1e6ce9cbde4076f3bd998ec6ad7ef52ce365123987f2f7a86e328e9c4c
MD5 af8f531e238fe4920469a45e36e29e6c
BLAKE2b-256 b94328c200ada88bc13300e90fe3e9d2f102681836886f29d5c36545177983d7

See more details on using hashes here.

Provenance

The following attestation bundles were made for netlicensing_client-0.1.0.tar.gz:

Publisher: netlicensing-publish-pypi.yml on Labs64/NetLicensingClient-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file netlicensing_client-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for netlicensing_client-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 db686ddd41cd8f48619d6906ce3864b54fb9d0ba50c7338d56d70cc368956b25
MD5 06a1dbd2ebe1e8f7faad7ae7a65eed4d
BLAKE2b-256 49054845b970f4bc532b167e06bed64502b1171d810832f07e6efc54ff027ea3

See more details on using hashes here.

Provenance

The following attestation bundles were made for netlicensing_client-0.1.0-py3-none-any.whl:

Publisher: netlicensing-publish-pypi.yml on Labs64/NetLicensingClient-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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