Skip to main content

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

Metadata

Release files for netlicensing-client 0.1.0

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

Source distribution (sdist)

Source distribution for netlicensing-client 0.1.0
File Size Uploaded Provenance Actions
netlicensing_client-0.1.0.tar.gz 41.3 kB With provenance Details for netlicensing_client-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for netlicensing-client 0.1.0
File Interpreter ABI Platform Actions
netlicensing_client-0.1.0-py3-none-any.whl Python 3 none any Details for netlicensing_client-0.1.0-py3-none-any.whl

Total release size: 76.5 kB

Release files / netlicensing_client-0.1.0.tar.gz

Download URL netlicensing_client-0.1.0.tar.gz
Size 41.3 kB
Tags Source
SHA-256 checksum
How to use checksums
bdea5c1e6ce9cbde4076f3bd998ec6ad7ef52ce365123987f2f7a86e328e9c4c
BLAKE2b-256 checksum
How to use checksums
b94328c200ada88bc13300e90fe3e9d2f102681836886f29d5c36545177983d7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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 May 15, 2026.

Transparency log

Release files / netlicensing_client-0.1.0-py3-none-any.whl

Download URL netlicensing_client-0.1.0-py3-none-any.whl
Size 35.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
db686ddd41cd8f48619d6906ce3864b54fb9d0ba50c7338d56d70cc368956b25
BLAKE2b-256 checksum
How to use checksums
49054845b970f4bc532b167e06bed64502b1171d810832f07e6efc54ff027ea3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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 May 15, 2026.

Transparency log

Security for netlicensing-client 0.1.0

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for netlicensing_client-0.1.0.tar.gz

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 May 15, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

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