> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ancestraldev.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Integrating Avalex License Validation in Python Applications

> How to validate Avalex software licenses in Python with cross-platform HWID generation and robust error handling for rate limits and network failures.

This guide walks you through embedding Avalex license validation in a Python application. You will generate a stable hardware identifier at startup, post it to the `POST /licenses/validate` endpoint alongside your license and product IDs, and handle every failure mode — including rate limiting and network outages — so your users always receive a clear and appropriate response.

## Installation

The example below relies on the `requests` library. Install it with pip before proceeding:

```bash theme={null}
pip install requests
```

If you are working in a virtual environment (recommended), activate it first before running the command above.

## Full Example

The following module is self-contained and ready to drop into your project. Adjust `AVALEX_API_URL` and `PRODUCT_ID` to match your deployment.

```python theme={null}
import hashlib
import platform
import subprocess
import requests
import time

AVALEX_API_URL = "http://localhost:8080/licenses/validate"
PRODUCT_ID = "prod_enterprise_2026"

def get_hwid() -> str:
    system = platform.system()
    raw_id = ""
    try:
        if system == "Windows":
            cmd = "wmic csproduct get uuid"
            raw_id = subprocess.check_output(cmd, shell=True).decode().split('\n')[1].strip()
        elif system == "Linux":
            with open("/etc/machine-id", "r") as f:
                raw_id = f.read().strip()
        elif system == "Darwin":
            cmd = "ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformUUID"
            raw_id = subprocess.check_output(cmd, shell=True).decode().split('"')[3].strip()
    except Exception:
        raw_id = platform.node() + platform.machine()
    return hashlib.sha256(raw_id.encode()).hexdigest().upper()[:32]

def validate_license(license_id: str) -> bool:
    hwid = get_hwid()
    payload = {
        "licenseId": license_id,
        "productId": PRODUCT_ID,
        "hwid": hwid
    }
    try:
        response = requests.post(AVALEX_API_URL, json=payload, timeout=5)
        if response.status_code == 200:
            data = response.json()
            return data.get("valid", False)
        elif response.status_code == 429:
            print("[Avalex] Rate limit reached. Backing off.")
            return False
        else:
            print(f"[Avalex] Server responded with error status: {response.status_code}")
            return False
    except requests.RequestException as e:
        print(f"[Avalex] Connection failed: {e}")
        return False

if __name__ == "__main__":
    user_license = "LIC-8829-X19"
    is_valid = validate_license(user_license)
    if is_valid:
        print("License active. Starting application...")
    else:
        print("Invalid or expired license. Exiting.")
```

## Code Walkthrough

**HWID generation (`get_hwid`)**

`get_hwid` reads the most stable machine-unique identifier available on each platform — the BIOS/UEFI product UUID on Windows, `/etc/machine-id` on Linux, and the `IOPlatformUUID` on macOS. If all platform-specific methods fail, it falls back to a combination of the hostname and machine architecture. The raw string is then hashed with SHA-256 and the first 32 hex characters are returned. This keeps the HWID short and consistent across calls without exposing the raw hardware value.

**Request construction**

`validate_license` builds a JSON payload with the three required fields — `licenseId`, `productId`, and `hwid` — and posts it to the API with a 5-second timeout. The timeout prevents your application from hanging indefinitely when the validation server is unreachable.

**Response handling**

A `200` status code means the server responded successfully. The function reads the `valid` boolean from the JSON body and returns it directly. Any value other than `True` (including a missing key) returns `False`, so your application defaults to a locked state on ambiguous responses.

**429 rate-limit handling**

When the server returns `429 Too Many Requests`, the function logs a warning and returns `False`. You should combine this with your offline cache logic — a `429` is not a license rejection, so you can treat it the same as a network failure and fall back to the cached grace period instead of locking the user out.

**Network error handling**

The `except requests.RequestException` block catches DNS failures, connection timeouts, SSL errors, and all other transport-level problems. Again, return `False` here and let your caching layer decide whether to allow or deny the session.

<Note>
  Replace `AVALEX_API_URL` with your production URL and ensure it uses **HTTPS**. The `http://localhost:8080` address is for local development only and must not be shipped in a production build.
</Note>

<Tip>
  Cache the last valid timestamp to disk — ideally in an encrypted format — so your application can support an offline grace period of 24–72 hours. This prevents a temporary network outage or API maintenance window from locking out legitimate users.
</Tip>
