Python client library for the Labs64 NetLicensing RESTful API
Project description
Labs64 NetLicensing Client (Python)
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
- Quickstart
- Configuration and Authentication
- Services
- Pagination and Iteration
- Error Handling
- Context Manager
- Models and Enums
- Custom Properties
- Demo
- Development
- References
- How to Contribute
- Bugs and Feedback
- License
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
- NetLicensing RESTful API
- NetLicensing Security / API Key Identification
- NetLicensing Management Console
- NetLicensing Licensing Models
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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bdea5c1e6ce9cbde4076f3bd998ec6ad7ef52ce365123987f2f7a86e328e9c4c
|
|
| MD5 |
af8f531e238fe4920469a45e36e29e6c
|
|
| BLAKE2b-256 |
b94328c200ada88bc13300e90fe3e9d2f102681836886f29d5c36545177983d7
|
Provenance
The following attestation bundles were made for netlicensing_client-0.1.0.tar.gz:
Publisher:
netlicensing-publish-pypi.yml on Labs64/NetLicensingClient-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
netlicensing_client-0.1.0.tar.gz -
Subject digest:
bdea5c1e6ce9cbde4076f3bd998ec6ad7ef52ce365123987f2f7a86e328e9c4c - Sigstore transparency entry: 1546644624
- Sigstore integration time:
-
Permalink:
Labs64/NetLicensingClient-python@b75aad53978d37200c11ca8846365f19d0b1c37f -
Branch / Tag:
refs/tags/0.1.0 - Owner: https://github.com/Labs64
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
netlicensing-publish-pypi.yml@b75aad53978d37200c11ca8846365f19d0b1c37f -
Trigger Event:
release
-
Statement type:
File details
Details for the file netlicensing_client-0.1.0-py3-none-any.whl.
File metadata
- Download URL: netlicensing_client-0.1.0-py3-none-any.whl
- Upload date:
- Size: 35.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
db686ddd41cd8f48619d6906ce3864b54fb9d0ba50c7338d56d70cc368956b25
|
|
| MD5 |
06a1dbd2ebe1e8f7faad7ae7a65eed4d
|
|
| BLAKE2b-256 |
49054845b970f4bc532b167e06bed64502b1171d810832f07e6efc54ff027ea3
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
netlicensing_client-0.1.0-py3-none-any.whl -
Subject digest:
db686ddd41cd8f48619d6906ce3864b54fb9d0ba50c7338d56d70cc368956b25 - Sigstore transparency entry: 1546644669
- Sigstore integration time:
-
Permalink:
Labs64/NetLicensingClient-python@b75aad53978d37200c11ca8846365f19d0b1c37f -
Branch / Tag:
refs/tags/0.1.0 - Owner: https://github.com/Labs64
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
netlicensing-publish-pypi.yml@b75aad53978d37200c11ca8846365f19d0b1c37f -
Trigger Event:
release
-
Statement type: