Link
✎

How to Implement Software Licensing in Python

There’s no NetLicensing client library for Python yet, but honestly you don’t need one: the REST API is simple enough to call directly. Send an HTTP POST to https://go.netlicensing.io/core/v2/rest/licensee/{licenseeNumber}/validate with your API key, read the result for your product module, and cache it until its ttl. The example below uses the requests package and Python 3.11 or later.

If you’d rather manage NetLicensing from AI tooling than validate licenses, have a look at the NetLicensing MCP server, which is distributed as the netlicensing-mcp Python package.

Validate a customer

from datetime import datetime, timezone
import requests

API_KEY = "YOUR_API_KEY"  # role ROLE_APIKEY_LICENSEE
HOSTS = ["https://go.netlicensing.io", "https://netlicensing.labs64.com"]  # primary, backup


def validate(licensee_number, product_number=None, module_params=None, timeout=10):
    """Return (results_by_module, ttl) for a licensee.

    module_params: {"M-FLOAT": {"sessionId": "host-1", "action": "checkOut"}, ...}
    """
    data = {}
    if product_number:
        data["productNumber"] = product_number  # needed only for licensee auto-create
    for i, (module, params) in enumerate((module_params or {}).items()):
        data[f"productModuleNumber{i}"] = module
        for key, value in params.items():
            data[f"{key}{i}"] = value

    last_error = None
    for host in HOSTS:
        try:
            response = requests.post(
                f"{host}/core/v2/rest/licensee/{licensee_number}/validate",
                data=data,
                auth=("apiKey", API_KEY),
                headers={"Accept": "application/json"},
                timeout=timeout,
            )
            break
        except requests.ConnectionError as error:  # DNS or network failure: try the backup host
            last_error = error
    else:
        raise last_error

    body = response.json()
    if response.status_code >= 400:
        messages = [info["value"] for info in (body.get("infos") or {}).get("info", [])]
        raise RuntimeError(f"Validation failed ({response.status_code}): {'; '.join(messages)}")

    results = {}
    for item in (body.get("items") or {}).get("item", []):
        if item.get("type") != "ProductModuleValidation":
            continue
        props = {p["name"]: p["value"] for p in item.get("property", [])}
        props["lists"] = {
            lst["name"]: {p["name"]: p["value"] for p in lst.get("property", [])}
            for lst in item.get("list", []) or []
        }
        results[props["productModuleNumber"]] = props

    ttl = datetime.fromisoformat(body["ttl"].replace("Z", "+00:00")) if body.get("ttl") else None
    return results, ttl


results, ttl = validate("CUSTOMER-123", product_number="YOUR_PRODUCT_NUMBER")
module = results.get("YOUR_PRODUCT_MODULE_NUMBER", {})
licensed = module.get("valid") == "true"

Every value in the JSON response is a string. Nested list entries carry per-feature or per-license results, for example for Multi-Feature, Pricing Table or Rental modules. The example gathers them under lists, keyed by name.

Model-specific parameters

# Node-Locked: device fingerprint generated by your application
validate("CUSTOMER-123", module_params={"M-NODE": {"nodeSecret": fingerprint}})

# Floating: check out a concurrent session
validate("CUSTOMER-123", module_params={"M-FLOAT": {"sessionId": session_id, "action": "checkOut"}})

# Pay-per-Use: report consumed credits (post-payment mode)
validate("CUSTOMER-123", module_params={"M-PPU": {"usedQuantity": "5"}})

Cache the result

_cache = {}

def is_licensed(licensee_number, module_number):
    cached = _cache.get(licensee_number)
    if not cached or not cached[1] or datetime.now(timezone.utc) >= cached[1]:
        cached = _cache[licensee_number] = validate(licensee_number)
    return cached[0].get(module_number, {}).get("valid") == "true"

If the API can’t be reached, keep using the cached result until its TTL runs out, then fall back on your own grace rules.

Signed responses and offline files

Response signature verification (XML Signature) is implemented in the Java and C# clients, and for offline files in the PHP client, but not in this example. If you need signed responses or offline validation files in Python, request XML (Accept: application/xml) and verify the XML signature with an XMLDSig library such as signxml against your public key before trusting the result. See Signing the Response and Offline License Validation.

See also

Last updated · Edit this page on GitHub