License Management API Documentation

Complete API reference and integration guide for license validation and management. Build secure, scalable license validation into your applications with our comprehensive API.

🚀 What You'll Find Here

  • Complete API Reference: All endpoints with request/response examples
  • Integration Guides: Step-by-step implementation instructions
  • Code Examples: Ready-to-use code in Python, JavaScript, and C#
  • Security Features: Hardware fingerprinting, fraud detection, and more
  • Production Ready: Deployment, monitoring, and best practices

Quick Start

Get up and running with the License Management API in minutes.

1. Get Your API Credentials

HTTP
POST /api/auth/login
Content-Type: application/json

{
  "email": "admin@licensemanagement.com",
  "password": "your_password"
}

2. Validate a License

HTTP
POST /api/validation/validate
Content-Type: application/json

{
  "license_key": "ABCD-EFGH-IJKL-MNOP",
  "software_api_key": "sk_1234567890abcdef1234567890abcdef1234567890abcdef"
}

3. Handle the Response

Python
import requests
import platform
import psutil

# Generate hardware fingerprint
hardware_fingerprint = {
    'cpu': platform.processor(),
    'memory': f"{psutil.virtual_memory().total // (1024**3)}GB",
    'os': f"{platform.system()} {platform.release()}",
    'device_id': 'unique_device_identifier'
}

response = requests.post('http://localhost:5000/api/validation/validate', json={
    'license_key': 'ABCD-EFGH-IJKL-MNOP',
    'software_api_key': 'sk_1234567890abcdef1234567890abcdef1234567890abcdef',
    'user_email': 'user@example.com',
    'hardware_fingerprint': hardware_fingerprint
})

if response.status_code == 200:
    data = response.json()
    if data['valid']:
        print("✅ License is valid!")
        print(f"Expires: {data['license']['expires_at']}")
        print(f"License activations: {data['license']['current_activations']}/{data['license']['max_activations']}")

        # Device activation info
        if 'device_activation' in data:
            device = data['device_activation']
            print(f"Device: {'New' if device['is_new_device'] else 'Existing'}")
            print(f"Device activations: {device['activation_count']}")
    else:
        print("❌ License is invalid")
        print(f"Error: {data.get('message', 'Unknown error')}")
else:
    print(f"Error: {response.status_code}")
JavaScript
// Generate hardware fingerprint
const hardwareFingerprint = {
    cpu: navigator.userAgentData?.platform || navigator.platform,
    memory: `${navigator.deviceMemory || 'Unknown'}GB`,
    screen_resolution: `${screen.width}x${screen.height}`,
    os: navigator.userAgentData?.platform || navigator.platform,
    browser: navigator.userAgent,
    device_id: 'unique_device_identifier'
};

const response = await fetch('http://localhost:5000/api/validation/validate', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
        license_key: 'ABCD-EFGH-IJKL-MNOP',
        software_api_key: 'sk_1234567890abcdef1234567890abcdef1234567890abcdef',
        user_email: 'user@example.com',
        hardware_fingerprint: hardwareFingerprint
    })
});

const data = await response.json();

if (response.ok && data.valid) {
    console.log('✅ License is valid!');
    console.log(`Expires: ${data.license.expires_at}`);
    console.log(`License activations: ${data.license.current_activations}/${data.license.max_activations}`);

    // Device activation info
    if (data.device_activation) {
        const device = data.device_activation;
        console.log(`Device: ${device.is_new_device ? 'New' : 'Existing'}`);
        console.log(`Device activations: ${device.activation_count}`);
    }
} else {
    console.log('❌ License is invalid');
    console.log(`Error: ${data.message || 'Unknown error'}`);
}
C#
using var client = new HttpClient();
var payload = new {
    license_key = "ABCD-EFGH-IJKL-MNOP",
    software_api_key = "sk_1234567890abcdef1234567890abcdef1234567890abcdef"
};

var response = await client.PostAsJsonAsync(
    "http://localhost:5000/api/validation/validate",
    payload
);

if (response.IsSuccessStatusCode) {
    var data = await response.Content.ReadFromJsonAsync();
    if (data.valid) {
        Console.WriteLine("✅ License is valid!");
        Console.WriteLine($"Expires: {data.license.expires_at}");
    }
}

Authentication & Connection

The License Management API uses JWT (JSON Web Tokens) for authentication. All protected endpoints require a valid access token.

🔑 Authentication Flow

  1. Login: Exchange credentials for access and refresh tokens
  2. Access: Use access token for API requests (24-hour expiry)
  3. Refresh: Use refresh token to get new access tokens (30-day expiry)
  4. Logout: Invalidate tokens when done

Base URL Configuration

Development: http://localhost:5000

Production: https://api.yourdomain.com

Login Endpoint

POST /api/auth/login

Request
{
  "email": "admin@licensemanagement.com",
  "password": "admin123"
}
Response (200)
{
  "message": "Login successful",
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
  "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
  "expires_in": 86400,
  "user": {
    "id": 1,
    "email": "admin@licensemanagement.com",
    "role": "admin"
  }
}

Using Access Tokens

Include the access token in the Authorization header for all protected requests:

HTTP Headers
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
Content-Type: application/json

Software API Key Authentication

For license validation from client applications, use software API keys instead of JWT tokens. Each software entry automatically generates a unique API key when created.

🔑 API Key Format

  • Prefix: sk_ (software key)
  • Length: 51 characters total (3 prefix + 48 hex characters)
  • Example: sk_1234567890abcdef1234567890abcdef1234567890abcdef

🛡️ Security Features

  • Unique per Software: Each software application has its own API key
  • Cryptographically Secure: Generated using secrets.token_hex(24)
  • Database Constraints: Unique constraint prevents duplicates
  • Regeneration Support: API keys can be regenerated for security

📍 Where to Find Your API Key

  1. Navigate to Dashboard → Software Management
  2. View the API Key column in the software table
  3. Click the View button for detailed software information
  4. Copy the API key using the copy button

Authentication API

User authentication, JWT token management, and session handling endpoints.

🔐 Key Features

  • JWT Authentication: Secure token-based authentication
  • Role-Based Access: Admin, Manager, and User role management
  • Token Refresh: Automatic token renewal for seamless sessions
  • User Registration: Account creation with email verification

User Login

POST /api/auth/login • No Authentication Required

Authenticate user credentials and receive JWT access and refresh tokens.

Request
{
  "email": "admin@example.com",
  "password": "securepassword123"
}
Response (200)
{
  "message": "Login successful",
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
  "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
  "user": {
    "id": 1,
    "email": "admin@example.com",
    "name": "System Administrator",
    "role": "admin",
    "status": "active"
  }
}
Error Response (401)
{
  "message": "Invalid email or password"
}

Refresh Token

POST /api/auth/refresh • Requires Refresh Token

Refresh an expired access token using a valid refresh token.

Headers
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
Response (200)
{
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
  "message": "Token refreshed successfully"
}

Get Current User

GET /api/auth/me • Requires Authentication

Get information about the currently authenticated user.

Response (200)
{
  "user": {
    "id": 1,
    "email": "admin@example.com",
    "name": "System Administrator",
    "role": "admin",
    "status": "active",
    "created_at": "2025-01-01T10:00:00Z",
    "last_login": "2025-07-10T15:30:00Z"
  }
}

User Registration

POST /api/auth/register • No Authentication Required

Register a new user account with email verification.

Request
{
  "email": "newuser@example.com",
  "password": "securepassword123",
  "name": "John Smith",
  "phone": "+1234567890",
  "country_code": "+1"
}
Response (201)
{
  "message": "User registered successfully",
  "user": {
    "id": 25,
    "email": "newuser@example.com",
    "name": "John Smith",
    "role": "user",
    "status": "active",
    "created_at": "2025-07-10T15:30:00Z"
  }
}

Users API

Manage user accounts, roles, and contact information.

List Users

GET /api/users • Requires Authentication

Query Parameters

  • page (int): Page number (default: 1)
  • per_page (int): Items per page (default: 20)
  • role (string): Filter by role (admin, manager, user)
  • status (string): Filter by status (active, inactive, suspended)
  • search (string): Search by name or email
Request
GET /api/users?page=1&per_page=20&role=user
Authorization: Bearer 
Response (200)
{
  "users": [
    {
      "id": 1,
      "uuid": "550e8400-e29b-41d4-a716-446655440000",
      "email": "user@example.com",
      "first_name": "John",
      "last_name": "Doe",
      "phone": "+1234567890",
      "whatsapp": "+1234567890",
      "telegram": "@johndoe",
      "country_code": "+1",
      "role": "user",
      "status": "active",
      "email_verified": true,
      "two_factor_enabled": false,
      "created_at": "2024-01-01T00:00:00Z",
      "updated_at": "2024-01-15T10:30:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "per_page": 20,
    "total": 1,
    "pages": 1
  }
}

Create User

POST /api/users • Requires Authentication (Admin/Manager)

Request
{
  "email": "newuser@example.com",
  "password": "SecurePassword123!",
  "first_name": "Jane",
  "last_name": "Smith",
  "phone": "+1987654321",
  "whatsapp": "+1987654321",
  "telegram": "@janesmith",
  "country_code": "+1",
  "role": "user"
}

Users Statistics

GET /api/users/stats • Requires Authentication (Admin/Manager)

Get comprehensive user statistics for dashboard overview cards.

Response (200)
{
  "total_users": 150,
  "active_users": 142,
  "users_with_active_licenses": 89,
  "recent_registrations": 12
}

Licenses API

Create, manage, and validate software licenses.

Create License

POST /api/licenses • Requires Authentication (Admin/Manager)

Request
{
  "user_email": "customer@mail.com",
  "license_type": "trial",
  "device_limit": 1,
  "validity_value": 72,
  "validity_unit": "hours",
  "start_on_activation": true,
  "features": ["feature.id"],
  "send_email": true
}

Validity & delayed start. Send the duration as validity_value + validity_unit (days | hours | minutes) — or the legacy validity_days. 0 / omitted = never expires. The duration is stored server-side as seconds and expires_at is precise to the second.

With "start_on_activation": true the license ships dormant: it is fully usable immediately, but expires_at stays NULL and the validity clock only starts on the license's first device activation (activated_at records the moment; later deactivations/reactivations never restart it). Such licenses appear under the Pending activation status. Plans can carry a default validity too (validity_seconds).

Response (201)
{
  "message": "License created successfully",
  "license": {
    "id": 123,
    "uuid": "550e8400-e29b-41d4-a716-446655440001",
    "license_key": "ABCD-EFGH-IJKL-MNOP",
    "software_id": 1,
    "user_id": 456,
    "type": "ultimate",
    "status": "active",
    "expires_at": "2025-01-15T10:30:00Z",
    "max_activations": 3,
    "current_activations": 0,
    "allow_deactivation": true,
    "created_at": "2024-01-15T10:30:00Z"
  }
}

List Licenses — filters & CSV export

GET /api.php?endpoint=licenses • Requires Authentication • App selected

Query parameters: page, per_page, search (key/email), type, and status — one of all | active | pending | expiring | expired | inactive (expiring = active and expiring within 7 days; pending = start-on-activation licenses whose clock hasn't started yet). Add &export=csv to download the whole filtered set as CSV instead of JSON.

Example
GET /api.php?endpoint=licenses&page=1&search=customer@mail.com&status=expiring
GET /api.php?endpoint=licenses&status=expired&export=csv

Extend Licenses (single or bulk)

POST /api.php?endpoint=licenses/extend • Requires Authentication • App selected

Adds time to one or many licenses: send value + unit (days | hours | minutes), or legacy days. Extends from the later of (now, current expiry): expired licenses restart from today, future ones keep their runway. Dormant (pending-activation) licenses get their stored duration extended, so they still receive the full added time after first activation. Perpetual (no-expiry) licenses are skipped.

Request
{
  "ids": [123, 124, 125],
  "value": 6,
  "unit": "hours"
}
Response
{
  "success": true,
  "extended": 2,
  "skipped_perpetual": 1,
  "not_found": 0,
  "message": "Extended 2 license(s) by 6 hours. 1 perpetual license(s) skipped."
}

Reduce Licenses (single or bulk)

POST /api.php?endpoint=licenses/reduce • Requires Authentication • App selected

Subtracts time from one or many licenses: send value + unit (days | hours | minutes), or legacy days. The expiry is pulled earlier by the given amount — the result may land in the past, expiring the license immediately. Only licenses with a running clock are touched: perpetual (no-expiry) licenses and already-expired ones are skipped, and a pending-activation license's stored duration shrinks only while time would remain (a reduction consuming all of it is skipped).

Request
{
  "ids": [123, 124, 125],
  "value": 7,
  "unit": "days"
}
Response
{
  "success": true,
  "reduced": 1,
  "skipped_perpetual": 0,
  "skipped_expired": 1,
  "skipped_too_small": 1,
  "not_found": 0,
  "message": "Reduced 1 license(s) by 7 days. skipped 1 already expired, skipped 1 (reduction exceeds remaining validity)."
}

Resend License Email

POST /api.php?endpoint=licenses/resend • Requires Authentication • App selected

Re-emails an existing license key to its customer. Requires email delivery to be enabled in Settings with an SMTP server (or the host's mailer) configured.

Request
{ "id": 123 }

Send Test Email

POST /api.php?endpoint=email-test • Requires Authentication • App selected

Sends a short test message through the configured transport — the same pipeline license emails use. Configure SMTP in Settings (smtp_host, smtp_port, smtp_secure = tls/ssl/none, smtp_username, smtp_password, plus email_enabled, email_from_name, email_from_email). Without smtp_host, PHP mail() is used as a fallback. Saving an empty smtp_password keeps the stored one.

Request
{ "to": "you@example.com" }

Change Own Password

POST /api.php?endpoint=change-password • Requires Authentication

Changes the logged-in admin's password. The current password is required so a hijacked session cannot silently take over the account. Minimum 8 characters.

Request
{
  "current_password": "old-pass",
  "new_password": "new-strong-pass"
}

Activity Log Export & Device Details

GET /api.php?endpoint=activity&export=csv • Requires Authentication • App selected

The activity log supports the same filters as the Activity page (q, action, status, email, days) — add &export=csv to download. Likewise GET /api.php?endpoint=license-devices/{id}&export=csv exports a license's devices (now including CPU, hostname, MAC address and fingerprint hash, which are also returned in the JSON listing).

Example
GET /api.php?endpoint=activity&action=heartbeat&days=7&export=csv
GET /api.php?endpoint=license-devices/123&export=csv

License Validation API

The core endpoint for validating licenses in your applications.

🔍 Key Features

  • Hardware Fingerprinting: Unique device identification
  • Fraud Detection: ML-powered risk assessment
  • Geographic Validation: Location-based restrictions
  • Real-time Monitoring: Live license status updates

Validate License

POST /api/validation/validate • No Authentication Required (uses software API key)

Validates a license key and tracks device activation. This endpoint implements comprehensive device activation tracking:

  • New Device Activation: When a new device (unique hardware fingerprint) validates a license, it increments the license's current_activations count
  • Existing Device Re-validation: When an existing device re-validates, it updates the device's activation_count and last_seen timestamp without incrementing the license activation count
  • Activation Limit Enforcement: New device activations are blocked when the license's max_activations limit is reached
  • Device Identification: Uses SHA256 hashing of hardware fingerprint data for consistent device identification
  • Comprehensive Response: Returns detailed device activation information including device ID, activation count, timestamps, and new device flag
Request
{
  "license_key": "ABCD-EFGH-IJKL-MNOP",
  "software_api_key": "sk_1234567890abcdef1234567890abcdef1234567890abcdef",
  "user_email": "user@example.com",
  "hardware_fingerprint": {
    "cpu": "Intel Core i7-9700K",
    "memory": "16GB",
    "storage": "512GB SSD",
    "mac_address": "00:1B:44:11:3A:B7",
    "screen_resolution": "1920x1080",
    "os": "Windows 10 Pro",
    "browser": "Chrome 91.0.4472.124",
    "motherboard": "ASUS ROG STRIX Z390-E",
    "gpu": "NVIDIA GeForce RTX 3080",
    "device_id": "device001"
  },
  "ip_address": "192.168.1.100",
  "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}

Request Parameters

Parameter Type Required Description
license_key string Yes The license key to validate (format: XXXX-XXXX-XXXX-XXXX)
software_api_key string Yes Software API key for authentication (format: sk_[random])
user_email string No Email address of the license owner (for email-license validation)
hardware_fingerprint object No Hardware fingerprint data for device activation tracking
ip_address string No Client IP address for fraud detection
user_agent string No Client user agent for fraud detection
Response (200) - Valid License
{
  "valid": true,
  "message": "License is valid",
  "license": {
    "id": 123,
    "license_key": "ABCD-EFGH-IJKL-MNOP",
    "type": "ultimate",
    "status": "active",
    "expires_at": "2024-12-31T23:59:59Z",
    "max_activations": 3,
    "current_activations": 1,
    "created_at": "2024-01-01T00:00:00Z",
    "last_validated": "2024-07-10T14:30:00Z"
  },
  "software": {
    "id": 1,
    "name": "YouTube CPM GoLogin Automation Software",
    "version": "1.0.1",
    "max_devices_per_license": 3,
    "allow_offline_validation": true,
    "require_hardware_fingerprint": true,
    "fraud_detection_enabled": true
  },
  "device_activation": {
    "device_id": "b926d58a-d55e-40ea-b9db-b7b2a20d2fe0",
    "fingerprint_hash": "a0a109eeb0ee4283...",
    "is_new_device": false,
    "activation_count": 3,
    "first_seen": "2024-07-01T10:15:30.123456",
    "last_seen": "2024-07-10T14:30:00.654321"
  },
  "validation_timestamp": "2024-07-10T14:30:00Z",
  "hardware_fingerprint_received": true
}

Response Fields (Success)

Field Type Description
valid boolean Whether the license validation was successful
message string Human-readable validation result message
license object License information including status, expiration, and activation counts
software object Software information including name, version, and configuration
device_activation object Device activation tracking information (when hardware_fingerprint provided)
device_activation.device_id string Unique device identifier (UUID)
device_activation.fingerprint_hash string Truncated SHA256 hash of hardware fingerprint (for security)
device_activation.is_new_device boolean Whether this is a new device activation (true) or existing device re-validation (false)
device_activation.activation_count integer Number of times this specific device has validated the license
device_activation.first_seen string ISO timestamp when this device first activated the license
device_activation.last_seen string ISO timestamp when this device last validated the license
validation_timestamp string ISO timestamp of the validation request
hardware_fingerprint_received boolean Whether hardware fingerprint data was included in the request
Response (403) - Expired License
{
  "valid": false,
  "message": "License has expired",
  "error_code": "LICENSE_EXPIRED",
  "license": {
    "id": 123,
    "license_key": "ABCD-EFGH-IJKL-MNOP",
    "status": "expired",
    "expires_at": "2023-12-31T23:59:59Z"
  }
}
Response (401) - Invalid API Key
{
  "valid": false,
  "message": "Invalid software API key",
  "error_code": "INVALID_API_KEY"
}
Response (404) - License Not Found
{
  "valid": false,
  "message": "License not found",
  "error_code": "LICENSE_NOT_FOUND"
}
Response (400) - Missing Parameters
{
  "valid": false,
  "message": "Missing required parameters: license_key, software_api_key",
  "error_code": "MISSING_PARAMETERS"
}
Response (403) - Activation Limit Exceeded
{
  "valid": false,
  "message": "License cannot be activated: maximum activations reached or license inactive",
  "error_code": "ACTIVATION_LIMIT_EXCEEDED"
}
Response (403) - Email-License Mismatch
{
  "valid": false,
  "message": "Email-license key mismatch: provided email does not match license owner",
  "error_code": "EMAIL_LICENSE_MISMATCH"
}

Licenses Statistics

GET /api/licenses/stats • Requires Authentication

Get comprehensive license statistics for dashboard overview cards. Regular users see only their own license statistics.

Response (200) - Admin/Manager
{
  "total_licenses": 450,
  "active_licenses": 380,
  "expired_licenses": 45,
  "total_revenue": 37999.20
}
Response (200) - Regular User
{
  "total_licenses": 3,
  "active_licenses": 2,
  "expired_licenses": 1,
  "total_revenue": 199.98
}

Device Management API

Manage device activations and hardware fingerprints for licenses.

🔧 Key Features

  • Device Tracking: Monitor all devices activated for each license
  • Remote Deactivation: Remove devices from licenses remotely
  • Activation Management: View device activation history and statistics
  • Security Monitoring: Track device fingerprints and trust levels

Get License Devices

GET /api/licenses/{license_id}/devices • Requires Authentication

Retrieve all devices activated for a specific license. Regular users can only view devices for their own licenses.

Request
GET /api/licenses/123/devices
Authorization: Bearer <access_token>
Response (200)
{
  "devices": [
    {
      "id": 1,
      "uuid": "30963ab6-dbea-42c6-8459-c5de221a96ea",
      "device_id": "30963ab6-dbea-42c6-8459-c5de221a96ea",
      "fingerprint_hash": "a1b2c3d4e5f6...",
      "first_seen": "2024-07-01T10:15:30.123456",
      "last_seen": "2024-07-10T14:30:00.654321",
      "activation_count": 15,
      "trust_level": "high",
      "quality_score": 0.95,
      "device_info": {
        "cpu": "Intel Core i7-9700K",
        "memory": "16GB",
        "os": "Windows 10 Pro",
        "browser": "Chrome 91.0.4472.124"
      }
    }
  ],
  "total_devices": 1,
  "license": {
    "id": 123,
    "license_key": "ABCD-EFGH-IJKL-MNOP",
    "current_activations": 1,
    "max_activations": 3
  }
}

Remove Device from License

DELETE /api/licenses/{license_id}/devices/{device_id} • Requires Authentication (Admin/Manager)

Remove a specific device from a license. This decreases the license's current activation count.

Request
DELETE /api/licenses/123/devices/30963ab6-dbea-42c6-8459-c5de221a96ea
Authorization: Bearer <access_token>
Response (200)
{
  "message": "Device removed successfully",
  "license": {
    "id": 123,
    "license_key": "ABCD-EFGH-IJKL-MNOP",
    "current_activations": 0,
    "max_activations": 3
  }
}

Clear All License Devices

DELETE /api/licenses/{license_id}/devices • Requires Authentication (Admin/Manager)

Remove all devices from a license. This resets the license's current activation count to 0.

Request
DELETE /api/licenses/123/devices
Authorization: Bearer <access_token>
Response (200)
{
  "message": "All 2 devices cleared successfully",
  "devices_removed": 2,
  "license": {
    "id": 123,
    "license_key": "ABCD-EFGH-IJKL-MNOP",
    "current_activations": 0,
    "max_activations": 3
  }
}

License Deactivation

POST /api/validation/deactivate • No Authentication Required (uses software API key)

Deactivate a license on the current device. This endpoint is designed for client software to deactivate licenses when uninstalling or transferring to another device.

Request
{
  "license_key": "ABCD-EFGH-IJKL-MNOP",
  "software_api_key": "sk_1234567890abcdef1234567890abcdef1234567890abcdef",
  "hardware_fingerprint": {
    "cpu": "Intel Core i7-9700K",
    "memory": "16GB",
    "storage": "512GB SSD",
    "mac_address": "00:1B:44:11:3A:B7",
    "os": "Windows 10 Pro",
    "device_id": "device001"
  }
}
Response (200)
{
  "message": "License deactivated successfully",
  "success": true,
  "license": {
    "license_key": "ABCD-EFGH-IJKL-MNOP",
    "current_activations": 2,
    "max_activations": 3,
    "status": "active"
  }
}

Device Abuse Guard API

The device abuse guard protects trial licenses: when a device's last license (per app) was a trial, activating a different trial on it — under any account — is refused with DEVICE_ABUSE_BLOCKED until an admin releases the device. Paid licenses (pro / business / plans) are never blocked, and any paid activation resets the device's chain (trial → pro → trial is allowed). Each trial license carries a device_guard flag (on by default, controllable via the Create/Edit License modal) — an unguarded trial neither blocks nor marks devices. Guard history survives deactivation, device removal and license deletion, so removing a license is never an escape hatch.

Admin monitoring and release controls live on the Trial Devices page in the admin panel (Devices / Abuse Alerts tabs, with a pending-alerts badge in the sidebar).

Blocked Activation Response

POST /api/devices/activate • returned when the guard fires

Activating a guarded trial on a device whose last license was a different trial returns 403 with a machine-readable code. Before the block: the device cannot be hard-removed from a guarded trial either — POST /api/devices/delete and DELETE /api/devices answer 403 with "Trial devices cannot be permanently removed. Deactivate the device instead." Deactivation remains the supported exit, and re-activating the same license on the same device always works.

Response (403)
{
  "success": false,
  "error": "License abuse not allowed: this device was already used with a trial license. Please purchase a license or contact support.",
  "code": "DEVICE_ABUSE_BLOCKED"
}

The desktop client should surface the error message; the code field lets clients branch (e.g. show a purchase link).

List Guarded Devices

GET /api/trial-devices?page=1&per_page=25&q=<search> • Admin (session or X-Admin-Key)

Lists one row per device the guard has seen (its last license per app), newest activation first. q searches device ID, email and license key; allowlisted=1|0 filters by status.

Response (200)
{
  "data": [
    {
      "id": 12,
      "device_id": "devGuard-01",
      "device_hw_hash": "9f2a…e1",
      "last_license_id": 341,
      "last_license_key": "ABCD-EFGH-IJKL-MNOP",
      "last_license_type": "trial",
      "last_user_email": "user@example.com",
      "last_trial_at": "2026-08-30 10:14:00",
      "last_activation_at": "2026-08-30 10:14:00",
      "trial_count": 1,
      "is_allowlisted": 0,
      "note": null
    }
  ],
  "total": 1, "page": 1, "per_page": 25
}

Allowlist / Re-guard a Device

POST /api/trial-devices/allowlist • Admin

Releases (or re-arms) the guard for a whole device. Allowlisted devices can activate any license.

Request
{ "id": 12, "allowlisted": 1, "note": "approved by support" }
Response (200)
{ "success": true, "message": "Device allowlisted — guard released." }

Forget a Device (reset its history)

DELETE /api/trial-devices/{id} • Admin

Removes the device's guard record, its approvals and auto-resolves its pending alerts — fresh trials are allowed on it again.

Response (200)
{ "success": true, "message": "Device record reset. Fresh trials are allowed on it again." }

List Abuse Alerts

GET /api/abuse-alerts?status=pending&page=1 • Admin

One alert per (device, attempted license). Repeat blocked attempts bump attempts and re-pend a resolved alert. status accepts pending, resolved or all (default).

Response (200)
{
  "data": [
    {
      "id": 7,
      "device_id": "devGuard-01",
      "license_id": 342,
      "license_key": "KLMN-OPQR-STUV-WXYZ",
      "user_email": "other@example.com",
      "attempts": 2,
      "first_attempt_at": "2026-08-30 10:20:03",
      "last_attempt_at": "2026-08-30 10:22:41",
      "status": 1,
      "resolved_action": null,
      "resolved_by": null,
      "resolved_at": null,
      "note": null
    }
  ],
  "total": 1, "page": 1, "per_page": 25
}

Resolve an Abuse Alert

POST /api/abuse-alerts/resolve • Admin

approve_device allowlists the device (everything is allowed on it). approve_license grants a narrow exemption for exactly this license + device. dismiss only closes the alert. After any approval the user simply retries activation.

Request
{ "id": 7, "action": "approve_license", "note": "verified purchase" }
Response (200)
{ "success": true, "message": "Alert resolved (approve_license)." }

License Creation Parameter

POST /api/licenses   PUT /api/licenses/{id} • Admin

Trial licenses accept device_guard (boolean, default true). Set it to false to issue a trial that is fully outside the guard — it neither blocks a device nor marks it. The admin panel shows this as the "Device abuse guard" checkbox in the Create/Edit License modal (trial type only). Omitting the field on create keeps the default (on); on update, omitting it keeps the stored value.

Software API

Manage software applications, versions, and API keys.

Create Software

POST /api/software • Requires Authentication (Admin/Manager)

Required Fields

  • name (string): Software name
  • version (string): Software version (e.g., "1.0.0")
  • description (string): Software description

Optional Fields

  • status (string): Software status (default: "active")
  • max_activations (int): Max devices per license (default: 1)
  • allow_offline_validation (bool): Allow offline validation (default: true)
  • require_hardware_fingerprint (bool): Require hardware fingerprint (default: true)
  • fraud_detection_enabled (bool): Enable fraud detection (default: true)
Automatic API Key Generation: When software is created, a unique API key (format: sk_ + 48-character hex string) is automatically generated for license validation purposes.
Request
{
  "name": "YouTube CPM GoLogin Automation Software",
  "description": "Professional automation software for YouTube CPM optimization",
  "version": "1.0.0",
  "status": "active",
  "max_activations": 3,
  "allow_offline_validation": true,
  "require_hardware_fingerprint": true,
  "fraud_detection_enabled": true
}
Response (201)
{
  "message": "Software created successfully",
  "software": {
    "id": 1,
    "uuid": "550e8400-e29b-41d4-a716-446655440001",
    "name": "YouTube CPM GoLogin Automation Software",
    "description": "Professional automation software for YouTube CPM optimization",
    "version": "1.0.0",
    "api_key": "sk_1234567890abcdef1234567890abcdef1234567890abcdef",
    "status": "active",
    "license_validation_url": null,
    "webhook_url": null,
    "max_devices_per_license": 3,
    "allow_offline_validation": true,
    "require_hardware_fingerprint": true,
    "fraud_detection_enabled": true,
    "created_at": "2025-07-10T10:30:00Z",
    "updated_at": "2025-07-10T10:30:00Z",
    "active_licenses_count": 0,
    "total_licenses_count": 0,
    "recent_validations_count": 0,
    "security_events_count": 0,
    "fraud_detection_stats": {
      "total_attempts": 0,
      "blocked_attempts": 0,
      "fraud_score_avg": 0.0
    }
  }
}

List Software

GET /api/software • Requires Authentication (Admin/Manager)

Query Parameters

  • page (int): Page number (default: 1)
  • per_page (int): Items per page (default: 20, max: 100)
  • status (string): Filter by status (active, inactive, maintenance)
  • search (string): Search by name, description, or version
  • license_count_min (int): Filter by minimum license count
  • license_count_max (int): Filter by maximum license count
  • sort_by (string): Sort field (name, version, status, created_at)
  • sort_order (string): Sort order (asc, desc)
Response (200)
{
  "software": [
    {
      "id": 1,
      "uuid": "550e8400-e29b-41d4-a716-446655440001",
      "name": "YouTube CPM GoLogin Automation Software",
      "description": "Professional automation software for YouTube CPM optimization",
      "version": "1.0.1",
      "api_key": "sk_1234567890abcdef1234567890abcdef1234567890abcdef",
      "status": "active",
      "license_validation_url": null,
      "webhook_url": null,
      "max_devices_per_license": 3,
      "allow_offline_validation": true,
      "require_hardware_fingerprint": true,
      "fraud_detection_enabled": true,
      "created_at": "2025-07-01T10:00:00Z",
      "updated_at": "2025-07-10T15:30:00Z",
      "active_licenses_count": 2,
      "total_licenses_count": 4,
      "recent_validations_count": 15,
      "security_events_count": 0,
      "fraud_detection_stats": {
        "total_attempts": 25,
        "blocked_attempts": 0,
        "fraud_score_avg": 0.05
      }
    }
  ],
  "pagination": {
    "current_page": 1,
    "total_pages": 1,
    "total_items": 1,
    "items_per_page": 20,
    "start_item": 1,
    "end_item": 1,
    "has_prev": false,
    "has_next": false
  }
}

Update Software

PUT /api/software/{software_id} • Requires Authentication (Admin/Manager)

Updatable Fields

  • name (string): Software name
  • version (string): Software version
  • description (string): Software description
  • status (string): Software status (active, inactive, maintenance)
Request
{
  "name": "YouTube CPM GoLogin Automation Software Pro",
  "description": "Enhanced professional automation software for YouTube CPM optimization",
  "version": "1.1.0",
  "status": "active"
}
Response (200)
{
  "message": "Software updated successfully",
  "software": {
    "id": 1,
    "uuid": "550e8400-e29b-41d4-a716-446655440001",
    "name": "YouTube CPM GoLogin Automation Software Pro",
    "description": "Enhanced professional automation software for YouTube CPM optimization",
    "version": "1.1.0",
    "api_key": "sk_1234567890abcdef1234567890abcdef1234567890abcdef",
    "status": "active",
    "license_validation_url": null,
    "webhook_url": null,
    "max_devices_per_license": 3,
    "allow_offline_validation": true,
    "require_hardware_fingerprint": true,
    "fraud_detection_enabled": true,
    "created_at": "2025-07-10T10:30:00Z",
    "updated_at": "2025-07-10T15:45:00Z",
    "active_licenses_count": 2,
    "total_licenses_count": 4,
    "recent_validations_count": 15,
    "security_events_count": 0,
    "fraud_detection_stats": {
      "total_attempts": 25,
      "blocked_attempts": 0,
      "fraud_score_avg": 0.05
    }
  }
}

Get Software by ID

GET /api/software/{software_id} • Requires Authentication

Response (200)
{
  "software": {
    "id": 1,
    "uuid": "550e8400-e29b-41d4-a716-446655440001",
    "name": "YouTube CPM GoLogin Automation Software",
    "description": "Professional automation software for YouTube CPM optimization",
    "version": "1.0.1",
    "api_key": "sk_1234567890abcdef1234567890abcdef1234567890abcdef",
    "status": "active",
    "license_validation_url": null,
    "webhook_url": null,
    "max_devices_per_license": 3,
    "allow_offline_validation": true,
    "require_hardware_fingerprint": true,
    "fraud_detection_enabled": true,
    "created_at": "2025-07-01T10:00:00Z",
    "updated_at": "2025-07-10T15:30:00Z",
    "active_licenses_count": 2,
    "total_licenses_count": 4,
    "recent_validations_count": 15,
    "security_events_count": 0,
    "fraud_detection_stats": {
      "total_attempts": 25,
      "blocked_attempts": 0,
      "fraud_score_avg": 0.05
    }
  }
}

Delete Software

DELETE /api/software/{software_id} • Requires Authentication (Admin only)

⚠️ Warning: Software with active licenses cannot be deleted. Revoke or expire all licenses first.
Response (200)
{
  "message": "Software deleted successfully"
}
Error Response (400)
{
  "message": "Cannot delete software with 5 active licenses"
}

Bulk Software Operations

POST /api/software/bulk • Requires Authentication (Admin/Manager)

Supported Operations

  • delete: Delete multiple software (Admin only)
  • update_status: Update status of multiple software
Request (Delete)
{
  "operation": "delete",
  "software_ids": [1, 2, 3]
}
Request (Update Status)
{
  "operation": "update_status",
  "software_ids": [1, 2, 3],
  "status": "maintenance"
}
Response (200)
{
  "message": "Bulk operation completed: 2 successful, 1 failed",
  "results": {
    "success": [
      {
        "id": 1,
        "name": "Software A",
        "action": "deleted"
      },
      {
        "id": 2,
        "name": "Software B",
        "action": "deleted"
      }
    ],
    "failed": [
      {
        "id": 3,
        "name": "Software C",
        "error": "Cannot delete software with 5 active licenses"
      }
    ]
  }
}

Software Statistics

GET /api/software/statistics • Requires Authentication (Admin/Manager)

Get comprehensive software statistics for dashboard overview cards.

Response (200)
{
  "total_software": 15,
  "active_software": 12,
  "inactive_software": 2,
  "maintenance_software": 1,
  "total_licenses": 450,
  "total_revenue": 44999.50,
  "avg_licenses_per_software": 30.0,
  "top_performing_software": {
    "id": 1,
    "name": "YouTube CPM GoLogin Automation Software",
    "license_count": 125,
    "revenue": 12499.75
  }
}

Bulk Operations API

Perform batch operations on multiple entities for efficient management.

⚡ Key Features

  • Batch Processing: Operate on multiple entities simultaneously
  • Atomic Operations: All operations succeed or fail together
  • Detailed Results: Get success/failure status for each item
  • Permission Control: Role-based access for different operations

Bulk License Operations

POST /api/licenses/bulk • Requires Authentication (Admin/Manager)

Supported Operations

  • delete: Delete multiple licenses (Admin only)
  • update_status: Update status of multiple licenses
  • update_type: Update type of multiple licenses
  • update_expiration: Update expiration date of multiple licenses
Request - Update Status
{
  "operation": "update_status",
  "license_ids": [1, 2, 3, 4],
  "status": "suspended"
}
Request - Update Expiration
{
  "operation": "update_expiration",
  "license_ids": [1, 2, 3],
  "expires_at": "2024-12-31T23:59:59Z"
}
Response (200)
{
  "message": "Bulk operation completed: 3 successful, 1 failed",
  "results": {
    "success": [
      {"id": 1, "license_key": "ABCD-EFGH-IJKL-MNOP"},
      {"id": 2, "license_key": "WXYZ-1234-5678-9012"},
      {"id": 3, "license_key": "QRST-UVWX-YZAB-CDEF"}
    ],
    "failed": [
      {"id": 4, "error": "License not found"}
    ]
  }
}

Bulk User Operations

POST /api/users/bulk • Requires Authentication (Admin/Manager)

Supported Operations

  • delete: Delete multiple users (Admin only)
  • update_status: Update status of multiple users
  • update_role: Update role of multiple users
Important: You cannot perform bulk operations on yourself. Managers cannot modify admin users.
Request - Update Role
{
  "operation": "update_role",
  "user_ids": [2, 3, 4],
  "role": "manager"
}
Response (200)
{
  "message": "Bulk operation completed: 2 successful, 1 failed",
  "results": {
    "success": [
      {"id": 2, "email": "user2@example.com"},
      {"id": 3, "email": "user3@example.com"}
    ],
    "failed": [
      {"id": 4, "error": "Insufficient permissions to modify admin users"}
    ]
  }
}

Bulk Software Operations

POST /api/software/bulk • Requires Authentication (Admin/Manager)

Supported Operations

  • delete: Delete multiple software (Admin only)
  • update_status: Update status of multiple software
Request - Update Status
{
  "operation": "update_status",
  "software_ids": [1, 2, 3],
  "status": "maintenance"
}
Response (200)
{
  "message": "Bulk operation completed: 3 successful, 0 failed",
  "results": {
    "success": [
      {"id": 1, "name": "Software A"},
      {"id": 2, "name": "Software B"},
      {"id": 3, "name": "Software C"}
    ],
    "failed": []
  }
}

Version Control API

Manage software versions, changelogs, and version-specific license assignments.

🔄 Key Features

  • Semantic Versioning: Track MAJOR.MINOR.PATCH versions
  • Changelog Management: Document changes with categorization
  • Version Status: Control release status (draft, beta, stable, etc.)
  • Version-Specific Licensing: Assign licenses to specific versions

Get Software Versions

GET /api/versions/software/{software_id}/versions • Requires Authentication

Retrieve all versions for a specific software with optional filtering.

Query Parameters
include_changelog=true    # Include changelog entries with versions
status=stable             # Filter by release status (draft, beta, stable, etc.)
release_type=feature      # Filter by release type (feature, bugfix, security, etc.)
Response
{
  "success": true,
  "versions": [
    {
      "id": 1,
      "software_id": 1,
      "version_number": "1.0.0",
      "version_name": "Initial Release",
      "release_type": "stable",
      "release_status": "published",
      "release_notes": "First stable release",
      "breaking_changes": false,
      "is_latest": true,
      "download_url": "https://example.com/download/v1.0.0",
      "created_at": "2025-07-01T12:00:00Z",
      "published_at": "2025-07-05T10:30:00Z",
      "changelog_entries": [
        {
          "id": 1,
          "title": "Initial feature set",
          "change_type": "feature",
          "description": "Complete initial feature implementation"
        }
      ]
    }
  ],
  "total": 1
}

Create Software Version

POST /api/versions/software/{software_id}/versions • Requires Authentication (Admin, Manager)

Request
{
  "version_number": "1.1.0",
  "version_name": "Feature Update",
  "release_type": "feature",
  "release_notes": "Added new dashboard features",
  "breaking_changes": false,
  "is_lts": false
}
Response
{
  "message": "Version created successfully",
  "success": true,
  "version": {
    "id": 2,
    "software_id": 1,
    "version_number": "1.1.0",
    "version_name": "Feature Update",
    "release_type": "feature",
    "release_status": "draft",
    "created_at": "2025-07-10T14:30:00Z"
  }
}

Get Version Details

GET /api/versions/{version_id} • Requires Authentication

Response
{
  "success": true,
  "version": {
    "id": 1,
    "software_id": 1,
    "version_number": "1.0.0",
    "version_name": "Initial Release",
    "release_type": "stable",
    "release_status": "published",
    "release_notes": "First stable release",
    "breaking_changes": false,
    "is_latest": true,
    "download_url": "https://example.com/download/v1.0.0",
    "file_size": 15728640,
    "file_checksum": "a1b2c3d4e5f6...",
    "created_at": "2025-07-01T12:00:00Z",
    "published_at": "2025-07-05T10:30:00Z",
    "created_by": 1,
    "published_by": 1,
    "changelog_entries": [
      {
        "id": 1,
        "title": "Initial feature set",
        "change_type": "feature",
        "description": "Complete initial feature implementation",
        "technical_details": "Implemented core modules X, Y, and Z",
        "category": "core",
        "priority": "high",
        "affects_api": true,
        "affects_ui": true,
        "affects_database": false,
        "requires_migration": false,
        "created_at": "2025-07-01T14:20:00Z"
      }
    ]
  }
}

Update Version

PUT /api/versions/{version_id} • Requires Authentication (Admin, Manager)

Request
{
  "version_name": "Updated Release Name",
  "release_notes": "Updated release notes with more details",
  "breaking_changes": true,
  "migration_guide": "Follow these steps to migrate from v0.9.x...",
  "download_url": "https://example.com/download/v1.0.0-updated",
  "file_size": 16777216,
  "file_checksum": "updated-checksum-hash"
}

Publish Version

POST /api/versions/{version_id}/publish • Requires Authentication (Admin, Manager)

Request
{
  "set_as_latest": true
}
Response
{
  "success": true,
  "message": "Version published successfully",
  "version": {
    "id": 2,
    "version_number": "1.1.0",
    "release_status": "published",
    "is_latest": true,
    "published_at": "2025-07-15T09:45:00Z"
  }
}

Set Latest Version

POST /api/versions/{version_id}/set-latest • Requires Authentication (Admin, Manager)

Response
{
  "success": true,
  "message": "Version set as latest successfully"
}

Get Version Changelog

GET /api/versions/{version_id}/changelog • Requires Authentication

Query Parameters
change_type=feature     # Filter by change type (feature, bugfix, security, etc.)
category=ui             # Filter by category (ui, api, core, etc.)
Response
{
  "success": true,
  "entries": [
    {
      "id": 1,
      "version_id": 1,
      "title": "New dashboard UI",
      "change_type": "feature",
      "description": "Completely redesigned dashboard with improved UX",
      "category": "ui",
      "priority": "high",
      "affects_ui": true,
      "created_at": "2025-07-01T14:20:00Z"
    },
    {
      "id": 2,
      "version_id": 1,
      "title": "Performance improvements",
      "change_type": "enhancement",
      "description": "Optimized dashboard loading speed by 50%",
      "category": "ui",
      "priority": "medium",
      "affects_ui": true,
      "created_at": "2025-07-01T15:30:00Z"
    }
  ],
  "total": 2
}

Add Changelog Entry

POST /api/versions/{version_id}/changelog • Requires Authentication (Admin, Manager)

Request
{
  "change_type": "bugfix",
  "title": "Fixed login issue",
  "description": "Resolved authentication failure on certain browsers",
  "technical_details": "Fixed CORS headers and cookie handling",
  "category": "security",
  "priority": "critical",
  "affects_api": true,
  "affects_ui": false,
  "issue_number": "ISSUE-123",
  "pull_request_number": "PR-456"
}
Response
{
  "success": true,
  "message": "Changelog entry added successfully",
  "entry": {
    "id": 3,
    "version_id": 1,
    "title": "Fixed login issue",
    "change_type": "bugfix",
    "category": "security",
    "priority": "critical",
    "created_at": "2025-07-15T11:20:00Z"
  }
}

Update Changelog Entry

PUT /api/changelog/{entry_id} • Requires Authentication (Admin, Manager)

Request
{
  "title": "Updated entry title",
  "description": "Updated description with more details",
  "priority": "high",
  "affects_database": true,
  "requires_migration": true
}

Delete Changelog Entry

DELETE /api/changelog/{entry_id} • Requires Authentication (Admin, Manager)

Response
{
  "success": true,
  "message": "Changelog entry deleted successfully"
}

Webhooks

Receive real-time notifications about license events in your application.

🔔 Webhook Events

  • license.created: New license created
  • license.activated: License activated on a device
  • license.deactivated: License deactivated from a device
  • license.expired: License has expired
  • license.suspended: License suspended by admin
  • license.extended: License expiration extended
  • license.reduced: License expiration reduced
  • fraud.detected: Suspicious activity detected

Webhook Configuration

Configure webhooks when creating or updating software:

Configuration
{
  "webhook_url": "https://yourapp.com/webhooks/license",
  "webhook_secret": "your_webhook_secret_key",
  "webhook_events": [
    "license.created",
    "license.activated",
    "license.expired",
    "fraud.detected"
  ]
}

Webhook Payload

Example Payload
{
  "event": "license.activated",
  "timestamp": "2024-01-15T10:30:00Z",
  "data": {
    "license": {
      "id": 123,
      "license_key": "ABCD-EFGH-IJKL-MNOP",
      "type": "ultimate",
      "status": "active",
      "expires_at": "2024-12-31T23:59:59Z",
      "user_id": 456
    },
    "device": {
      "fingerprint_hash": "abc123def456",
      "ip_address": "192.168.1.100",
      "location": "New York, US"
    },
    "software": {
      "id": 1,
      "name": "My Application",
      "version": "2.1.0"
    }
  },
  "signature": "sha256=a1b2c3d4e5f6..."
}

Webhook Security

Verify webhook authenticity using HMAC-SHA256 signatures:

Python
import hmac
import hashlib
from flask import Flask, request, abort

app = Flask(__name__)
WEBHOOK_SECRET = "your_webhook_secret_key"

@app.route('/webhooks/license', methods=['POST'])
def handle_webhook():
    # Get the signature from headers
    signature = request.headers.get('X-Signature-256')
    if not signature:
        abort(400, 'Missing signature')

    # Calculate expected signature
    payload = request.get_data()
    expected_signature = 'sha256=' + hmac.new(
        WEBHOOK_SECRET.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()

    # Verify signature
    if not hmac.compare_digest(signature, expected_signature):
        abort(401, 'Invalid signature')

    # Process webhook
    data = request.get_json()
    event = data['event']

    if event == 'license.activated':
        handle_license_activation(data['data'])
    elif event == 'license.expired':
        handle_license_expiration(data['data'])
    elif event == 'fraud.detected':
        handle_fraud_detection(data['data'])

    return {'status': 'success'}

def handle_license_activation(data):
    license_key = data['license']['license_key']
    print(f"License {license_key} activated!")

def handle_license_expiration(data):
    license_key = data['license']['license_key']
    print(f"License {license_key} expired!")

def handle_fraud_detection(data):
    license_key = data['license']['license_key']
    print(f"Fraud detected for license {license_key}!")
Node.js
const express = require('express');
const crypto = require('crypto');
const app = express();

const WEBHOOK_SECRET = 'your_webhook_secret_key';

app.use(express.raw({ type: 'application/json' }));

app.post('/webhooks/license', (req, res) => {
    const signature = req.headers['x-signature-256'];

    if (!signature) {
        return res.status(400).send('Missing signature');
    }

    // Calculate expected signature
    const expectedSignature = 'sha256=' + crypto
        .createHmac('sha256', WEBHOOK_SECRET)
        .update(req.body)
        .digest('hex');

    // Verify signature
    if (!crypto.timingSafeEqual(
        Buffer.from(signature),
        Buffer.from(expectedSignature)
    )) {
        return res.status(401).send('Invalid signature');
    }

    // Process webhook
    const data = JSON.parse(req.body);
    const event = data.event;

    switch (event) {
        case 'license.activated':
            handleLicenseActivation(data.data);
            break;
        case 'license.expired':
            handleLicenseExpiration(data.data);
            break;
        case 'fraud.detected':
            handleFraudDetection(data.data);
            break;
    }

    res.json({ status: 'success' });
});

function handleLicenseActivation(data) {
    const licenseKey = data.license.license_key;
    console.log(`License ${licenseKey} activated!`);
}

function handleLicenseExpiration(data) {
    const licenseKey = data.license.license_key;
    console.log(`License ${licenseKey} expired!`);
}

function handleFraudDetection(data) {
    const licenseKey = data.license.license_key;
    console.log(`Fraud detected for license ${licenseKey}!`);
}
PHP
 'success']);

function handleLicenseActivation($data) {
    $licenseKey = $data['license']['license_key'];
    error_log("License {$licenseKey} activated!");
}

function handleLicenseExpiration($data) {
    $licenseKey = $data['license']['license_key'];
    error_log("License {$licenseKey} expired!");
}

function handleFraudDetection($data) {
    $licenseKey = $data['license']['license_key'];
    error_log("Fraud detected for license {$licenseKey}!");
}
?>

Dashboard API

Comprehensive analytics and statistics endpoints for administrative dashboards and reporting.

📊 Key Features

  • Time-Range Filtering: Get statistics for specific time periods
  • Role-Based Access: Different data visibility based on user roles
  • Real-time Analytics: Live dashboard statistics and metrics
  • Revenue Tracking: Financial analytics and revenue calculations

Dashboard Statistics

GET /api/dashboard/stats • Requires Authentication

Get comprehensive dashboard statistics with optional time-range filtering.

Query Parameters

  • range (string): Time range filter (24h, 7d, 30d, 90d, 1y)
Response (200)
{
  "users": {
    "total": 150,
    "active": 142,
    "new_registrations": 12
  },
  "licenses": {
    "total": 450,
    "active": 380,
    "expired": 45,
    "revenue": 37999.20
  },
  "software": {
    "total": 15,
    "active": 12,
    "total_licenses": 450,
    "avg_licenses_per_software": 30.0
  },
  "time_range": "30d",
  "generated_at": "2025-07-10T15:30:00Z"
}

Advanced Analytics

GET /api/dashboard/analytics • Requires Authentication (Admin/Manager)

Get detailed analytics data including trends, performance metrics, and security insights.

Query Parameters

  • range (string): Time range filter (24h, 7d, 30d, 90d, 1y)
  • include_trends (bool): Include trend analysis (default: true)
  • include_security (bool): Include security metrics (default: true)
Response (200)
{
  "overview": {
    "total_revenue": 37999.20,
    "revenue_growth": 15.3,
    "license_activation_rate": 89.2,
    "user_retention_rate": 94.1
  },
  "trends": {
    "daily_registrations": [2, 3, 1, 4, 2, 5, 3],
    "daily_revenue": [299.99, 199.98, 99.99, 399.96, 199.98, 499.95, 299.99],
    "license_activations": [5, 7, 3, 8, 4, 9, 6]
  },
  "security": {
    "total_events": 1250,
    "high_risk_events": 23,
    "blocked_attempts": 45,
    "fraud_score_avg": 0.125
  },
  "top_performing": {
    "software": {
      "id": 1,
      "name": "YouTube CPM GoLogin Automation Software",
      "license_count": 125,
      "revenue": 12499.75
    },
    "user": {
      "id": 15,
      "name": "John Smith",
      "license_count": 8,
      "total_spent": 799.92
    }
  },
  "time_range": "30d",
  "generated_at": "2025-07-10T15:30:00Z"
}

Security API

Security event tracking, fraud detection statistics, and security monitoring endpoints.

🔒 Key Features

  • Event Tracking: Monitor security events and suspicious activities
  • Fraud Detection: ML-powered fraud scoring and risk assessment
  • Real-time Monitoring: Live security statistics and alerts
  • Risk Analysis: Comprehensive security metrics and trends

Security Statistics

GET /api/security/stats • Requires Authentication (Admin/Manager)

Get comprehensive security statistics including event counts, fraud metrics, and risk analysis.

Query Parameters

  • hours (int): Time range in hours (default: 24, max: 8760)
Response (200)
{
  "total_events": 1250,
  "high_risk_events": 23,
  "blocked_attempts": 45,
  "fraud_score_avg": 0.125,
  "time_range_hours": 24,
  "event_breakdown": {
    "license_validation": 980,
    "failed_authentication": 45,
    "suspicious_activity": 23,
    "fraud_detection": 12,
    "hardware_mismatch": 8,
    "geographic_anomaly": 5
  },
  "risk_levels": {
    "low": 1180,
    "medium": 47,
    "high": 23,
    "critical": 0
  },
  "generated_at": "2025-07-10T15:30:00Z"
}

Statistics API

Comprehensive statistics and analytics endpoints for dashboard cards and reporting.

📊 Key Features

  • Real-time Data: Live statistics updated in real-time
  • Role-based Access: Different data visibility based on user roles
  • Comprehensive Metrics: Detailed breakdowns and analytics
  • Dashboard Integration: Optimized for dashboard card displays

License Statistics

GET /api/licenses/stats • Requires Authentication

Get license statistics for dashboard overview cards. Regular users see only their own license statistics.

Response (200) - Admin/Manager
{
  "total_licenses": 450,
  "active_licenses": 380,
  "expired_licenses": 45,
  "total_revenue": 37999.20
}

User Statistics

GET /api/users/stats • Requires Authentication (Admin/Manager)

Get user statistics for dashboard overview cards.

Response (200)
{
  "total_users": 125,
  "active_users": 118,
  "new_users_this_month": 12
}

Dashboard Statistics

GET /api/dashboard/stats • Requires Authentication

Query Parameters

  • range (string): Time range (24h, 7d, 30d, 12m) - default: 30d
Request
GET /api/dashboard/stats?range=30d
Authorization: Bearer <access_token>
Response (200)
{
  "revenue": {
    "total": 45999.50,
    "monthly": 3850.75,
    "growth": 12.5
  },
  "licenses": {
    "total": 450,
    "active": 380,
    "expired": 45,
    "trial": 25
  },
  "users": {
    "total": 125,
    "active": 118,
    "new_this_month": 12
  },
  "software": {
    "total": 8,
    "active": 7
  },
  "validations": {
    "total": 15420,
    "successful": 14890,
    "failed": 530,
    "success_rate": 96.6
  },
  "time_range": "30d",
  "generated_at": "2025-07-10T15:30:00Z"
}

Dashboard Analytics

GET /api/dashboard/analytics • Requires Authentication (Admin/Manager)

Get detailed analytics data including top software performance and trends.

Response (200)
{
  "top_software": [
    {
      "name": "YouTube CPM GoLogin Automation Software",
      "licenses": 125,
      "revenue": 12499.75
    },
    {
      "name": "Social Media Manager Pro",
      "licenses": 89,
      "revenue": 8899.11
    }
  ],
  "license_trends": {
    "daily_activations": [5, 8, 12, 6, 9, 15, 11],
    "daily_expirations": [2, 1, 3, 4, 2, 1, 2]
  },
  "validation_trends": {
    "hourly_validations": [45, 52, 38, 67, 89, 76, 54],
    "success_rates": [98.2, 97.8, 99.1, 96.5, 98.7, 97.9, 98.4]
  },
  "generated_at": "2025-07-10T15:30:00Z"
}

Announcements API

Manage in-app announcements to communicate with your users — push updates, warnings, and critical notices directly to client applications.

📢 Key Features

  • Public Fetch Endpoint: Client apps can fetch active announcements without authentication
  • Delivery & Read Tracking: When clients fetch announcements with their device_id, each announcement returned is recorded as delivered; when a client reports a view it's marked read. The admin panel shows Delivered / Read / Unread counts and the per-user list with status.
  • CRUD Operations: Full create, read, update, delete for admin users
  • Typed Announcements: Categorize as info, warning, update, or critical
  • Active/Inactive Toggle: Draft announcements and publish when ready
  • Paginated Listing: Server-side pagination, search, and type filtering for admin panel
  • App-Scoped: Each announcement belongs to a specific application

📋 Announcement Object

JSON
{
  "id": 1,
  "app_id": 3,
  "title": "v2.5.0 Released — Performance Boost",
  "message": "We've improved rendering speed by 40%. Update now to get the latest features.",
  "type": "update",
  "icon": "ph-megaphone",
  "is_active": 1,
  "created_at": "2025-07-10 14:30:00",
  "updated_at": "2025-07-10 14:30:00"
}
Field Type Description
idintUnique announcement ID
app_idintOwning application ID
titlestringAnnouncement headline
messagestringFull announcement body (supports HTML)
typeenumOne of: info, warning, update, critical
iconstringPhosphor Icons class name (e.g. ph-megaphone)
is_activeint (0|1)Whether the announcement is visible to clients
created_atdatetimeCreation timestamp
updated_atdatetimeLast update timestamp
view_countintDevices that reported reading this announcement. Admin list responses only.
delivered_countintDevices this announcement was delivered to (fetched with device_id). Unread = delivered_count − view_count. Admin list responses only.

Fetch Active Announcements (Public)

GET /api.php?endpoint=announcements&app_unique_id={app_unique_id} • No Authentication Required

Client applications call this endpoint to retrieve all active (published) announcements. The app is identified by either app_unique_id or app_name. Only announcements where is_active = 1 are returned.

Delivery tracking: if device_id is provided, every announcement returned in the response is recorded as delivered to that device (repeat fetches are deduplicated server-side and never overwrite read timestamps). Passing device_id also excludes announcements this device has already dismissed. Clients should always send their device_id here so the admin panel can show Delivered / Read / Unread statistics.

Query Parameters

Parameter Type Required Description
endpointstringYesMust be announcements
app_unique_idstringYes*App's unique identifier (e.g. 3181d3032534bcb13e2411b321dc17e0)
app_namestringYes*App name (alternative to app_unique_id)
device_idstringNoDevice identifier — records deliveries, excludes dismissed announcements

* Provide either app_unique_id or app_name — at least one is required.

Request
GET /api.php?endpoint=announcements&app_unique_id=3181d3032534bcb13e2411b321dc17e0
Response (200)
{
  "success": true,
  "announcements": [
    {
      "id": 1,
      "title": "v2.5.0 Released — Performance Boost",
      "message": "We've improved rendering speed by 40%...",
      "type": "update",
      "icon": "ph-megaphone",
      "created_at": "2025-07-10 14:30:00",
      "updated_at": "2025-07-10 14:30:00"
    },
    {
      "id": 2,
      "title": "Scheduled Maintenance — July 15",
      "message": "Services will be briefly unavailable...",
      "type": "warning",
      "icon": "ph-warning",
      "created_at": "2025-07-09 10:00:00",
      "updated_at": "2025-07-09 10:00:00"
    }
  ]
}
Error Response (404)
{
  "error": "App not found or inactive"
}

Record Announcement View (Public)

POST /api.php?endpoint=announcements/view&app_unique_id={app_unique_id} • Requires X-API-Key

Client applications call this endpoint when an announcement is displayed to the user to record a read receipt. The server deduplicates on (announcement_id, device_id), so calling it repeatedly is safe — only the first read per device is stored. Reporting a view also marks the announcement as delivered for that device (even if the delivery wasn't recorded at fetch time). The admin panel uses these records to show Delivered / Read / Unread counts and the per-user list with status.

Query Parameters

Parameter Type Required Description
endpointstringYesMust be announcements/view
app_unique_idstringYes*App's unique identifier
app_namestringYes*App name (alternative to app_unique_id)

* Provide either app_unique_id or app_name — at least one is required. The request must also send a valid X-API-Key header (the same key used for other client calls).

Request Body

Field Type Required Description
device_idstringYesThe device identifier the client uses for activation
announcement_idintYesID of the announcement being displayed
Request
POST /api.php?endpoint=announcements/view&app_unique_id=3181d3032534bcb13e2411b321dc17e0
X-API-Key: {your_api_key}
Content-Type: application/json

{
  "device_id": "0ba8dd16f3d7ec39a1e33721be7131dc53ae8c8dabdd690871979e0a920a95c7",
  "announcement_id": 1
}
Response (200)
{
  "success": true,
  "message": "View recorded."
}

Response Fields

Field Type Description
successboolWhether the view was recorded (or already existed — duplicates succeed silently)
messagestringHuman-readable confirmation

Errors: 400 if device_id or announcement_id is missing, 404 if the announcement doesn't exist or is inactive, 403 for a missing/invalid API key.

List All Announcements (Admin)

GET /api.php?endpoint=announcements • Requires Authentication

Retrieve a paginated list of all announcements for the currently selected app. Supports search and filtering.

Query Parameters

Parameter Type Default Description
pageint1Page number
per_pageint20Results per page
searchstring—Search in title and message
typestringallFilter by type: info, warning, update, critical
is_activeint (0|1)—Filter by active status
Request
GET /api.php?endpoint=announcements&page=1&per_page=10&search=maintenance&type=warning
Authorization: Bearer 
Response (200)
{
  "data": [
    {
      "id": 2,
      "app_id": 3,
      "title": "Scheduled Maintenance — July 15",
      "message": "Services will be briefly unavailable...",
      "type": "warning",
      "icon": "ph-warning",
      "is_active": 1,
      "created_at": "2025-07-09 10:00:00",
      "updated_at": "2025-07-09 10:00:00",
      "view_count": 7,
      "delivered_count": 12
    }
  ],
  "total": 1,
  "page": 1,
  "per_page": 10
}

Get Single Announcement (Admin)

GET /api.php?endpoint=announcements/{id} • Requires Authentication

Retrieve a single announcement by its ID.

Request
GET /api.php?endpoint=announcements/2
Authorization: Bearer 
Response (200)
{
  "id": 2,
  "app_id": 3,
  "title": "Scheduled Maintenance — July 15",
  "message": "Services will be briefly unavailable...",
  "type": "warning",
  "icon": "ph-warning",
  "is_active": 1,
  "created_at": "2025-07-09 10:00:00",
  "updated_at": "2025-07-09 10:00:00"
}

Get Announcement Readers (Admin)

GET /api.php?endpoint=announcements/{id}&action=readers • Requires Authentication

Retrieve the delivery/read status list for an announcement — used by the admin panel's Who Read This modal. Every device that received the announcement is listed with a Read or Unread status; rows where viewed_at is null were delivered but not read yet. Each row is resolved to the license holder's email via the device that reported the view. Entries from unrecognized devices are still included (with user_email as null) so nothing is silently dropped.

Query Parameters

Parameter Type Required Description
endpointstringYesMust be announcements/{id}
actionstringYesMust be readers
Request
GET /api.php?endpoint=announcements/2&action=readers
Authorization: Bearer 
Response (200)
{
  "success": true,
  "total": 2,
  "readers": [
    {
      "device_id": "0ba8dd16f3d7ec39a1e33721be7131dc53ae8c8dabdd690871979e0a920a95c7",
      "user_email": "adminuserstrong@gmail.com",
      "device_name": "TINY-FC60EC5F",
      "operating_system": "Windows 11 Pro",
      "delivered_at": "2025-07-10 14:30:05",
      "viewed_at": "2025-07-10 14:35:22"
    },
    {
      "device_id": "legacy-device-001",
      "user_email": "user@example.com",
      "device_name": "rdp-1",
      "operating_system": "Windows 10",
      "delivered_at": "2025-07-10 15:00:41",
      "viewed_at": null
    }
  ]
}

Reader Fields

Field Type Description
device_idstringDevice that reported the read
user_emailstring|nullLicense holder's email; null if the device can't be matched to a license
device_namestring|nullHuman-readable device name, if known
operating_systemstring|nullOS reported at activation, if known
delivered_atdatetimeWhen the announcement was delivered to this device (first fetch that included it)
viewed_atdatetime|nullWhen the device reported reading it; null means delivered but unread

Errors: 404 if the announcement doesn't exist or belongs to a different app.

Create Announcement (Admin)

POST /api.php?endpoint=announcements • Requires Authentication

Create a new announcement for the currently selected application.

Request Body

Field Type Required Default Description
titlestringYes—Announcement headline
messagestringYes—Full announcement body
typestringNoinfoOne of: info, warning, update, critical
iconstringNoph-megaphonePhosphor Icons class name
is_activeint (0|1)No1Set to 0 to create as draft
Request
POST /api.php?endpoint=announcements
Content-Type: application/json
Authorization: Bearer 

{
  "title": "v2.5.0 Released — Performance Boost",
  "message": "We've improved rendering speed by 40%. Update now to get the latest features.",
  "type": "update",
  "icon": "ph-rocket-launch",
  "is_active": 1
}
Response (200)
{
  "success": true,
  "id": 3,
  "message": "Announcement created successfully."
}
Error Response (400)
{
  "error": "Title and message are required"
}

Update Announcement (Admin)

PUT /api.php?endpoint=announcements/{id} • Requires Authentication

Update an existing announcement. Only announcements belonging to the currently selected app can be updated.

Request
PUT /api.php?endpoint=announcements/3
Content-Type: application/json
Authorization: Bearer 

{
  "title": "v2.5.1 Hotfix Released",
  "message": "Fixed a critical rendering bug. Please update immediately.",
  "type": "critical",
  "icon": "ph-warning-circle",
  "is_active": 1
}
Response (200)
{
  "success": true,
  "message": "Announcement updated successfully."
}
Error Response (404)
{
  "error": "Announcement not found or access denied"
}

Delete Announcement (Admin)

DELETE /api.php?endpoint=announcements/{id} • Requires Authentication

Permanently delete an announcement. Only announcements belonging to the currently selected app can be deleted.

Request
DELETE /api.php?endpoint=announcements/3
Authorization: Bearer 
Response (200)
{
  "success": true,
  "message": "Announcement deleted."
}
Error Response (404)
{
  "error": "Announcement not found or access denied"
}

Client Integration Example

Python
import requests

APP_UNIQUE_ID = "3181d3032534bcb13e2411b321dc17e0"
API_KEY = "your_api_key"
BASE_URL = "http://localhost/licensemanager"

def fetch_announcements(device_id):
    """Fetch active announcements from the license server.
    Pass device_id so deliveries are tracked and dismissed ones are excluded."""
    try:
        resp = requests.get(
            f"{BASE_URL}/api.php",
            params={
                "endpoint": "announcements",
                "app_unique_id": APP_UNIQUE_ID,
                "device_id": device_id,
            },
            timeout=10,
        )
        data = resp.json()
        if data.get("success"):
            return data.get("announcements", [])
        return []
    except Exception as e:
        print(f"Failed to fetch announcements: {e}")
        return []

def report_view(device_id, announcement_id):
    """Report that the user saw this announcement (read receipt).
    Duplicates are ignored server-side — call it every time you show one."""
    try:
        requests.post(
            f"{BASE_URL}/api.php",
            params={
                "endpoint": "announcements/view",
                "app_unique_id": APP_UNIQUE_ID,
            },
            headers={"X-API-Key": API_KEY},
            json={"device_id": device_id, "announcement_id": announcement_id},
            timeout=10,
        )
    except Exception as e:
        print(f"Failed to report view: {e}")

# Usage
DEVICE_ID = "your_device_id_here"
announcements = fetch_announcements(DEVICE_ID)
for ann in announcements:
    print(f"[{ann['type'].upper()}] {ann['title']}")
    print(f"  {ann['message'][:80]}...")
    print(f"  Posted: {ann['created_at']}")
    # Call this when the announcement UI is actually shown to the user,
    # so the admin panel's "Read by N" counter reflects real reads.
    report_view(DEVICE_ID, ann["id"])
    print()
JavaScript
const APP_UNIQUE_ID = "3181d3032534bcb13e2411b321dc17e0";
const API_KEY = "your_api_key";
const BASE_URL = "http://localhost/licensemanager";
const DEVICE_ID = "your_device_id_here";

async function fetchAnnouncements(deviceId) {
    // Pass device_id so deliveries are tracked and dismissed ones are excluded.
    try {
        const url = `${BASE_URL}/api.php?endpoint=announcements&app_unique_id=${APP_UNIQUE_ID}&device_id=${deviceId}`;
        const resp = await fetch(url);
        const data = await resp.json();

        if (data.success) {
            return data.announcements || [];
        }
        return [];
    } catch (err) {
        console.error("Failed to fetch announcements:", err);
        return [];
    }
}

// Report that the user saw this announcement (read receipt).
// Duplicates are ignored server-side — call it every time you show one.
async function reportView(announcementId) {
    try {
        const url = `${BASE_URL}/api.php?endpoint=announcements/view&app_unique_id=${APP_UNIQUE_ID}`;
        await fetch(url, {
            method: "POST",
            headers: {
                "X-API-Key": API_KEY,
                "Content-Type": "application/json",
            },
            body: JSON.stringify({ device_id: DEVICE_ID, announcement_id: announcementId }),
        });
    } catch (err) {
        console.error("Failed to report view:", err);
    }
}

// Usage
(async () => {
    const announcements = await fetchAnnouncements(DEVICE_ID);
    for (const ann of announcements) {
        console.log(`[${ann.type.toUpperCase()}] ${ann.title}`);
        console.log(`  ${ann.message.substring(0, 80)}...`);
        console.log(`  Posted: ${ann.created_at}`);
        // Call this when the announcement UI is actually shown to the user,
        // so the admin panel's "Read by N" counter reflects real reads.
        await reportView(ann.id);
    }
})();

💡 Best Practices

  • Polling interval: Client apps should poll every 30–60 minutes, not on every startup, to reduce server load.
  • Cache locally: Store fetched announcements with a timestamp and only re-fetch after the polling interval elapses.
  • Use types wisely: Reserve critical for security alerts or mandatory updates. Use info for general news.
  • Draft first: Create announcements with is_active: 0 and toggle to 1 when ready to publish.
  • Keep it brief: Titles should be under 80 characters. Use the message body for details.
  • Handle offline: If the fetch fails, show the last cached announcements rather than an empty state.
  • Report reads & deliveries: Always send device_id when fetching announcements (records deliveries, filters dismissed ones), and call announcements/view when an announcement is actually displayed to the user — together these power the admin panel's Delivered / Read / Unread statistics. Repeat calls are deduplicated server-side.

OTA Updates API

Deliver application updates over the air: push an installer from the admin panel, activate it, and every client app detects it through the public check-update endpoint, downloads it, verifies a SHA-256 checksum, and installs it.

🚀 Key Features

  • Public Check Endpoint: Client apps poll check-update with their app_unique_id — the same X-API-Key gate as announcements
  • Forced Updates: Tick Force update when pushing and clients cannot skip the version — it downloads and installs automatically
  • SHA-256 Checksums: fileHash is computed on upload so clients can verify their download
  • Version History: The public changelog endpoint returns every pushed version with its release notes for in-app "What's new" views
  • One Active Version: Activating a version automatically deactivates the previous one; clients always get the newest active update

Check for Update (Public)

GET /api.php?endpoint=check-update&app_unique_id={app_unique_id} • X-API-Key header required

Returns the newest active update for the app. Optional extra query parameters (current_version, device_id) are accepted for analytics and are ignored by older deployments.

JSON
{
  "latestVersion": "1.2.1",
  "releaseNotes": "[New] Force updates\n[Imp] faster startup\n[Fix] crash on export",
  "fileSize": 125829120,
  "fileHash": "9f2c6b...64-hex-sha256",
  "releaseDate": "2026-08-22",
  "downloadUrl": "https://licenses.example.com/lm/updates/Setup-1.2.1.zip",
  "forceUpdate": true
}

Important: when no update is activated, the server replies with HTTP 404 and a JSON body — do not treat the status code as an error:

JSON
{ "error": "No active update is available." }
Field Type Description
latestVersionstringVersion of the active update (compare semantically: MAJOR.MINOR.PATCH)
releaseNotesstringMulti-line notes, one change per line using [New] / [Imp] / [Fix] tags
fileSizeintInstaller size in bytes
fileHashstringSHA-256 hex of the file as served (64 chars); may be empty on very old uploads
releaseDatestringRelease date (YYYY-MM-DD)
downloadUrlstringFull HTTPS URL of the installer. Note: uploaded .exe files are stored and served as .zip (host restriction) — the bytes are unchanged, so save and run them as the original installer
forceUpdatebooltrue = forced update: clients must not offer skip/postpone; download and install automatically

Fetch Changelog (Public)

GET /api.php?endpoint=changelog&app_unique_id={app_unique_id} • X-API-Key header required

Version history for in-app "What's new" views — every pushed version, newest first (up to 50), including versions that are no longer the active update. isActive marks the currently live one.

JSON
{
  "success": true,
  "versions": [
    {
      "version": "1.2.1",
      "releaseNotes": "[New] Force updates\n[Fix] finisher hang",
      "fileSize": 125829120,
      "releaseDate": "2026-08-22",
      "forceUpdate": true,
      "isActive": true
    },
    {
      "version": "1.2.0",
      "releaseNotes": "[Imp] web GUI",
      "fileSize": 119600000,
      "releaseDate": "2026-08-20",
      "forceUpdate": false,
      "isActive": false
    }
  ]
}

🛠️ Pushing an Update (Admin Panel)

  1. Open OTA Updates in the admin panel (per selected app)
  2. Push New Update: pick the installer file (.exe, .zip, .msi, .apk… up to 1 GB) — the upload to the server starts immediately with a live progress bar while you fill in the rest of the form. Once it finishes, the uploaded file is shown in the modal with an Uploaded badge (you can still swap it via Change)
  3. Set the version (must be higher than your clients' current version; the next patch version is suggested automatically), add release notes one per line with [New] / [Imp] / [Fix] tags, optionally tick Force update — clients on older versions then cannot skip it
  4. Push Update submits the metadata and links the already-uploaded file — no second upload, so the final step is instant. Closing the modal without pushing deletes the staged file again
  5. Activate the version — this is what makes it visible to check-update (activating also deactivates the previous version)

Client responsibilities: compare latestVersion semantically against the running version, download downloadUrl over HTTPS, verify fileHash (SHA-256), then run the installer (NSIS-style installers accept /S for silent installation). Forced updates should download and install without offering a skip.

Stage an Update File (Admin)

POST /api.php?endpoint=updates-upload • Requires Admin Authentication (session cookie or X-Admin-Key) • multipart/form-data

Uploads the installer payload before the update row exists — the admin panel calls this the moment a file is picked so the user watches progress while filling in the form. The file lands in updates/ with the same validation as a direct push (extension whitelist, no hidden/dot files, 1 GB max; .exe is stored as .zip with bytes unchanged). No database row is created — the staged file only becomes an update when POST updates is called with uploaded_file.

Request (multipart)
POST /api.php?endpoint=updates-upload
Content-Type: multipart/form-data

update_file=@MyApp-1.2.2-Setup.exe
Response (200)
{
  "success": true,
  "file_name": "MyApp-1.2.2-Setup.zip",
  "file_size": 125829120,
  "message": "File uploaded. Fill in the details and push the update."
}

Persist the returned file_name and send it as uploaded_file in the subsequent POST updates metadata submission (all other fields — app_name, version, release_notes, developer_name, release_date, force_update — are identical to a direct push). Submitting the same staged file twice fails with HTTP 409.

Discard a Staged File (Admin)

DELETE /api.php?endpoint=updates-upload&file={file_name} • Requires Admin Authentication

Removes a staged file that was never pushed (the admin panel calls this when the push modal is cancelled or the file is swapped). Refuses with HTTP 409 if any updates row still references the file, and normalises the name with basename() so paths outside updates/ cannot be targeted.

Response (200)
{ "success": true, "message": "Staged file removed." }

Integration Guide

Step-by-step instructions for integrating license validation into your applications.

🚀 Integration Steps

  1. Obtain API Credentials: Create software entry and get API keys
  2. Implement Validation: Add license validation to your application
  3. Handle Responses: Process validation results and errors
  4. Add Security: Implement hardware fingerprinting and fraud detection
  5. Go Live: Deploy to production with monitoring

Step 1: Create Software Entry

HTTP
POST /api/software
Authorization: Bearer 
Content-Type: application/json

{
  "name": "Your Application Name",
  "description": "Your application description",
  "version": "1.0.0",
  "max_devices_per_license": 3,
  "require_hardware_fingerprint": true,
  "fraud_detection_enabled": true
}

Save the returned api_key securely. This key will be used for license validation requests.

Step 2: Implement Basic Validation

Python
import requests
import json

def validate_license(license_key, software_api_key):
    """Validate a license key"""
    url = "http://localhost:5000/api/validation/validate"
    payload = {
        "license_key": license_key,
        "software_api_key": software_api_key
    }

    try:
        response = requests.post(url, json=payload, timeout=30)
        data = response.json()

        if response.status_code == 200 and data.get('valid'):
            return {
                'valid': True,
                'license': data['license'],
                'expires_at': data['license']['expires_at']
            }
        else:
            return {
                'valid': False,
                'error': data.get('message', 'Unknown error'),
                'error_code': data.get('error_code')
            }
    except requests.exceptions.RequestException as e:
        return {
            'valid': False,
            'error': f'Network error: {str(e)}',
            'error_code': 'NETWORK_ERROR'
        }

# Usage
result = validate_license("ABCD-EFGH-IJKL-MNOP", "sk_1234567890abcdef1234567890abcdef1234567890abcdef")
if result['valid']:
    print("✅ License is valid!")
else:
    print(f"❌ License validation failed: {result['error']}")
JavaScript
async function validateLicense(licenseKey, softwareApiKey) {
    const url = 'http://localhost:5000/api/validation/validate';
    const payload = {
        license_key: licenseKey,
        software_api_key: softwareApiKey
    };

    try {
        const response = await fetch(url, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(payload)
        });

        const data = await response.json();

        if (response.ok && data.valid) {
            return {
                valid: true,
                license: data.license,
                expiresAt: data.license.expires_at
            };
        } else {
            return {
                valid: false,
                error: data.message || 'Unknown error',
                errorCode: data.error_code
            };
        }
    } catch (error) {
        return {
            valid: false,
            error: `Network error: ${error.message}`,
            errorCode: 'NETWORK_ERROR'
        };
    }
}

// Usage
const result = await validateLicense('ABCD-EFGH-IJKL-MNOP', 'sk_1234567890abcdef1234567890abcdef1234567890abcdef');
if (result.valid) {
    console.log('✅ License is valid!');
} else {
    console.log(`❌ License validation failed: ${result.error}`);
}
C#
using System.Text.Json;

public class LicenseValidator
{
    private readonly HttpClient _httpClient;

    public LicenseValidator()
    {
        _httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
    }

    public async Task ValidateLicenseAsync(
        string licenseKey,
        string softwareApiKey)
    {
        var url = "http://localhost:5000/api/validation/validate";
        var payload = new {
            license_key = licenseKey,
            software_api_key = softwareApiKey
        };

        try {
            var response = await _httpClient.PostAsJsonAsync(url, payload);
            var content = await response.Content.ReadAsStringAsync();
            var data = JsonDocument.Parse(content);

            if (response.IsSuccessStatusCode &&
                data.RootElement.GetProperty("valid").GetBoolean()) {
                return new LicenseValidationResult {
                    Valid = true,
                    ExpiresAt = data.RootElement
                        .GetProperty("license")
                        .GetProperty("expires_at")
                        .GetString()
                };
            } else {
                return new LicenseValidationResult {
                    Valid = false,
                    Error = data.RootElement.GetProperty("message").GetString(),
                    ErrorCode = data.RootElement.TryGetProperty("error_code", out var code)
                        ? code.GetString() : null
                };
            }
        } catch (Exception ex) {
            return new LicenseValidationResult {
                Valid = false,
                Error = $"Network error: {ex.Message}",
                ErrorCode = "NETWORK_ERROR"
            };
        }
    }
}

public class LicenseValidationResult
{
    public bool Valid { get; set; }
    public string ExpiresAt { get; set; }
    public string Error { get; set; }
    public string ErrorCode { get; set; }
}

Step 3: Handle Grace Periods

Implement grace periods for expired licenses to provide a better user experience:

JavaScript
function checkLicenseStatus(validationResponse) {
    if (!validationResponse.valid) {
        if (validationResponse.error_code === 'LICENSE_EXPIRED') {
            const expiredDate = new Date(validationResponse.license.expires_at);
            const gracePeriodDays = 7; // Configure as needed
            const gracePeriodEnd = new Date(
                expiredDate.getTime() + (gracePeriodDays * 24 * 60 * 60 * 1000)
            );

            if (new Date() <= gracePeriodEnd) {
                // Allow limited functionality during grace period
                return {
                    status: 'grace_period',
                    daysRemaining: Math.ceil(
                        (gracePeriodEnd - new Date()) / (24 * 60 * 60 * 1000)
                    ),
                    allowedFeatures: ['basic'] // Limit features
                };
            } else {
                return { status: 'expired', action: 'block_access' };
            }
        }
    }
    return { status: 'valid' };
}

Complete Code Examples

Production-ready code examples with error handling, caching, and retry logic.

Advanced Python Client

Python
import requests
import hashlib
import platform
import uuid
import time
import json
from datetime import datetime, timedelta
from typing import Dict, Optional, Tuple

class LicenseClient:
    def __init__(self, base_url: str, software_api_key: str):
        self.base_url = base_url.rstrip('/')
        self.software_api_key = software_api_key
        self.session = requests.Session()
        self.session.headers.update({
            'Content-Type': 'application/json',
            'X-Software-API-Key': software_api_key
        })

    def generate_hardware_fingerprint(self) -> Dict:
        """Generate hardware fingerprint for the current device"""
        try:
            import psutil
            memory_info = f"{round(psutil.virtual_memory().total / (1024**3))}GB"
            disk_info = f"{round(psutil.disk_usage('/').total / (1024**3))}GB"
        except ImportError:
            memory_info = "unknown"
            disk_info = "unknown"

        # Get MAC address
        mac = ':'.join(['{:02x}'.format((uuid.getnode() >> elements) & 0xff)
                       for elements in range(0,2*6,2)][::-1])

        return {
            "cpu": platform.processor(),
            "memory": memory_info,
            "storage": disk_info,
            "mac_address": mac,
            "os": f"{platform.system()} {platform.release()}",
            "python_version": platform.python_version(),
            "hostname": platform.node()
        }

    def validate_license_with_retry(self, license_key: str,
                                  max_retries: int = 3) -> Tuple[bool, Dict]:
        """Validate license with exponential backoff retry"""
        for attempt in range(max_retries):
            try:
                is_valid, data = self.validate_license(license_key)
                return is_valid, data
            except requests.exceptions.RequestException as e:
                if attempt == max_retries - 1:
                    return False, {"error": "network_error", "message": str(e)}

                # Exponential backoff
                delay = (2 ** attempt) + (time.time() % 1)
                time.sleep(delay)

        return False, {"error": "max_retries_exceeded"}

    def validate_license(self, license_key: str) -> Tuple[bool, Dict]:
        """Validate a license key"""
        payload = {
            "license_key": license_key,
            "software_api_key": self.software_api_key,
            "hardware_fingerprint": self.generate_hardware_fingerprint()
        }

        response = self.session.post(
            f"{self.base_url}/api/validation/validate",
            json=payload,
            timeout=30
        )

        data = response.json()
        return response.status_code == 200 and data.get('valid', False), data

# Usage
client = LicenseClient("http://localhost:5000", "sk_1234567890abcdef1234567890abcdef1234567890abcdef")
is_valid, response = client.validate_license_with_retry("ABCD-EFGH-IJKL-MNOP")

if is_valid:
    print("✅ License is valid!")
    print(f"Expires: {response['license']['expires_at']}")
else:
    print(f"❌ Validation failed: {response.get('message', 'Unknown error')}")

Error Handling

Comprehensive guide to handling API errors and implementing robust error recovery.

HTTP Status Codes

  • 200 OK: Request successful
  • 201 Created: Resource created successfully
  • 400 Bad Request: Invalid request data
  • 401 Unauthorized: Authentication required
  • 403 Forbidden: Access denied or license invalid
  • 404 Not Found: Resource not found
  • 429 Too Many Requests: Rate limit exceeded
  • 500 Internal Server Error: Server error

Error Response Format

JSON
{
  "error": true,
  "message": "Human-readable error message",
  "error_code": "MACHINE_READABLE_CODE",
  "details": {
    "field": "Additional error details",
    "validation_errors": ["List of validation issues"]
  },
  "timestamp": "2024-01-15T10:30:00Z",
  "request_id": "req_1234567890abcdef"
}

Common Error Codes

License Validation Errors

  • LICENSE_NOT_FOUND - License key doesn't exist
  • LICENSE_EXPIRED - License has expired
  • LICENSE_SUSPENDED - License is suspended
  • LICENSE_REVOKED - License has been revoked
  • MAX_ACTIVATIONS_EXCEEDED - Too many device activations
  • HARDWARE_MISMATCH - Device doesn't match fingerprint
  • FRAUD_DETECTED - Suspicious activity detected

Authentication Errors

  • INVALID_CREDENTIALS - Wrong email/password
  • TOKEN_EXPIRED - Access token has expired
  • TOKEN_INVALID - Malformed or invalid token
  • ACCOUNT_LOCKED - Account is locked due to failed attempts

Security Features

Advanced security features to protect your licenses and prevent fraud.

Hardware Fingerprinting

Hardware fingerprinting creates a unique identifier for each device to prevent license sharing.

Collected Data Points

  • CPU model and specifications
  • Memory configuration
  • Storage devices and capacity
  • MAC addresses
  • Operating system details
  • Screen resolution and display info
  • Browser fingerprint (for web apps)

Fraud Detection

ML-powered fraud detection analyzes patterns to identify suspicious activity:

Fraud Response
{
  "fraud_risk": {
    "score": 0.85,
    "level": "high",
    "factors": [
      "rapid_device_switching",
      "geographic_anomaly",
      "vpn_detected",
      "suspicious_timing"
    ],
    "recommended_action": "require_additional_verification"
  }
}

Rate Limiting

Default Limits

  • Authentication: 5 requests per minute
  • License Validation: 100 requests per minute
  • Management APIs: 60 requests per minute
  • Bulk Operations: 10 requests per minute

Rate limits are returned in response headers:

Headers
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1642248000

Testing & Validation

Test your integration with our comprehensive testing tools and procedures.

Test License Keys

Use these test license keys for development and testing:

  • TEST-VALID-LICENSE-KEY - Always returns valid
  • TEST-EXPIRED-LICENSE - Always returns expired
  • TEST-SUSPENDED-KEY - Always returns suspended
  • TEST-FRAUD-DETECTED - Triggers fraud detection
  • TEST-MAX-ACTIVATIONS - Exceeds activation limit

Integration Testing

Python Test Suite
import unittest
from license_client import LicenseClient

class TestLicenseValidation(unittest.TestCase):
    def setUp(self):
        self.client = LicenseClient(
            "http://localhost:5000",
            "sk_test_1234567890abcdef"
        )

    def test_valid_license(self):
        """Test valid license validation"""
        is_valid, response = self.client.validate_license("TEST-VALID-LICENSE-KEY")
        self.assertTrue(is_valid)
        self.assertIn('license', response)
        self.assertEqual(response['license']['status'], 'active')

    def test_expired_license(self):
        """Test expired license handling"""
        is_valid, response = self.client.validate_license("TEST-EXPIRED-LICENSE")
        self.assertFalse(is_valid)
        self.assertEqual(response['error_code'], 'LICENSE_EXPIRED')

    def test_fraud_detection(self):
        """Test fraud detection response"""
        is_valid, response = self.client.validate_license("TEST-FRAUD-DETECTED")
        self.assertFalse(is_valid)
        self.assertIn('fraud_risk', response)
        self.assertEqual(response['fraud_risk']['level'], 'high')

    def test_network_error_handling(self):
        """Test network error handling"""
        # Test with invalid URL
        client = LicenseClient("http://invalid-url", "test_key")
        is_valid, response = client.validate_license_with_retry("TEST-VALID-LICENSE-KEY")
        self.assertFalse(is_valid)
        self.assertIn('network_error', response['error'])

if __name__ == '__main__':
    unittest.main()

Production Deployment

Best practices for deploying the License Management API in production environments.

Environment Configuration

Environment Variables
# Database Configuration
DATABASE_URL=mysql://user:password@localhost/license_management
REDIS_URL=redis://localhost:6379/0

# Security
SECRET_KEY=your-super-secret-key-here
JWT_SECRET_KEY=your-jwt-secret-key
ENCRYPTION_KEY=your-32-byte-encryption-key

# API Configuration
API_BASE_URL=https://api.yourdomain.com
CORS_ORIGINS=https://yourdomain.com,https://app.yourdomain.com

# Rate Limiting
RATE_LIMIT_STORAGE_URL=redis://localhost:6379/1
DEFAULT_RATE_LIMIT=100

# Monitoring
SENTRY_DSN=https://your-sentry-dsn
LOG_LEVEL=INFO

# Email Configuration
SMTP_SERVER=smtp.yourdomain.com
SMTP_PORT=587
SMTP_USERNAME=noreply@yourdomain.com
SMTP_PASSWORD=your-smtp-password

Docker Deployment

docker-compose.yml
version: '3.8'

services:
  api:
    build: .
    ports:
      - "5000:5000"
    environment:
      - DATABASE_URL=mysql://user:password@db/license_management
      - REDIS_URL=redis://redis:6379/0
    depends_on:
      - db
      - redis
    restart: unless-stopped

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: rootpassword
      MYSQL_DATABASE: license_management
      MYSQL_USER: user
      MYSQL_PASSWORD: password
    volumes:
      - mysql_data:/var/lib/mysql
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    restart: unless-stopped

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./ssl:/etc/nginx/ssl
    depends_on:
      - api
    restart: unless-stopped

volumes:
  mysql_data:

Monitoring & Alerts

Key Metrics to Monitor

  • Response Time: API endpoint response times
  • Error Rate: 4xx and 5xx error percentages
  • Validation Rate: License validations per minute
  • Fraud Detection: High-risk validation attempts
  • Database Performance: Query execution times
  • Cache Hit Rate: Redis cache performance

Recommended Alerts

  • Error rate > 5% for 5 minutes
  • Response time > 2 seconds for 3 minutes
  • Database connection failures
  • High fraud risk score patterns
  • Unusual license validation patterns

Support & Resources

Get help and additional resources for your integration.

📞 Contact Information

  • Technical Support: support@licensemanagement.com
  • Sales Inquiries: sales@licensemanagement.com
  • Emergency Support: +1-555-0123 (24/7)
  • Documentation: docs.licensemanagement.com

🎯 Service Level Agreement

  • API Uptime: 99.9% guaranteed
  • Response Time: < 200ms average
  • Support Response: < 4 hours business days
  • Critical Issues: < 1 hour response time

🔄 Migration Assistance

Need help migrating from another license management system? Our team provides:

  • Data migration scripts and tools
  • Custom integration development
  • Dedicated migration support engineer
  • Testing and validation assistance