Skip to content

ValidonX Integration Guide — For Developers & AI Agents

Everything you need to add ValidonX license validation to your application.


Overview

ValidonX is a licensing-as-a-service platform. Your application calls the ValidonX API to validate license keys, check entitlements, track activations, and record usage. All API communication is server-side via HTTPS.

Base URL: https://api.validonx.com/api/v1/integration


Prerequisites

You need two things from ValidonX:

  1. API Key — provided by your ValidonX tenant administrator (format: vx_...)
  2. License Keys — issued to your end-users via the ValidonX dashboard (format: XXXX-XXXX-XXXX-XXXX)

Authentication

Every request requires the X-API-Key header:

X-API-Key: vx_your_api_key_here

Optional but recommended:

Content-Type: application/json
X-Request-ID: <uuid>

The X-Request-ID header enables request tracing across systems.


Core Workflow

A typical integration follows this flow:

1. User enters license key in your app
2. Your app calls ValidonX to validate the key
3. ValidonX returns valid/invalid + entitlements
4. Your app enforces features based on entitlements
5. (Optional) Your app creates an activation to track the device
6. (Optional) Your app records usage for metered features

API Endpoints

1. Validate a License Key

This is the primary endpoint. Call it on app startup or periodically to verify the user's license is valid.

POST /api/v1/integration/licenses/{licenseKey}/validate

Request (no body required):

bash
curl -X POST https://api.validonx.com/api/v1/integration/licenses/S00Z-RGB3-73G2-DIVH/validate \
  -H "X-API-Key: vx_your_api_key_here" \
  -H "Content-Type: application/json"

Success Response (200):

json
{
  "data": {
    "valid": true,
    "license": {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "key": "S00Z-RGB3-73G2-DIVH",
      "type": "subscription",
      "status": "active",
      "expires_at": "2027-04-05T00:00:00+00:00",
      "entitlements": {
        "plan": "pro",
        "order.prefix_suffix": true,
        "order.increment": true,
        "order.advanced_patterns": true,
        "activations.max": 5
      }
    }
  },
  "meta": {
    "request_id": "550e8400-e29b-41d4-a716-446655440000",
    "api_version": "1"
  }
}

Key fields:

  • data.validtrue if the license is active and not expired. This is the primary check.
  • data.license.statusactive, suspended, expired
  • data.license.entitlements — flexible key-value object containing the license's granted entitlements
  • data.license.expires_at — ISO 8601 timestamp, or null for perpetual licenses

Expired license (200, not an error):

json
{
  "data": {
    "valid": false,
    "license": {
      "id": "...",
      "key": "ND8Q-YIO8-CX4O-WVHE",
      "type": "subscription",
      "status": "expired",
      "expires_at": "2025-01-01T00:00:00+00:00",
      "entitlements": {
        "plan": "pro"
      }
    }
  }
}

Note: an expired license returns valid: false with the license data. It does not return an error. Only truly invalid keys return errors.


2. Create an Activation

Track a device or installation using the license. Use this to enforce activation limits (e.g., "pro plan allows 5 activations").

POST /api/v1/integration/activations

Request Body:

json
{
  "license_key": "S00Z-RGB3-73G2-DIVH",
  "fingerprint": "sha256-of-device-identifier",
  "metadata": {
    "os": "linux",
    "app_version": "2.1.0",
    "hostname": "server-prod-01"
  }
}
FieldRequiredDescription
license_keyYesThe license key to activate
fingerprintYesUnique device identifier (hash recommended)
metadataNoArbitrary JSON for your records

Response (201):

json
{
  "data": {
    "activation": {
      "id": "uuid",
      "license_key_id": "uuid",
      "fingerprint": "sha256-of-device-identifier",
      "status": "active",
      "metadata": { "os": "linux", "app_version": "2.1.0" },
      "created_at": "2026-04-11T10:00:00+00:00"
    }
  }
}

3. Validate an Activation

Check if a previously created activation is still valid.

POST /api/v1/integration/activations/{activationId}/validate

Response (200):

json
{
  "data": {
    "valid": true,
    "activation": {
      "id": "uuid",
      "status": "active"
    }
  }
}

4. Check Entitlements

Check specific entitlement codes for a license. Useful when you need detailed feature/limit info beyond what the validate response provides.

POST /api/v1/integration/entitlements/check

Request Body:

json
{
  "license_key": "S00Z-RGB3-73G2-DIVH",
  "features": ["activations.max", "order.advanced_patterns", "order.counter_reset"]
}

Response (200):

json
{
  "data": {
    "entitlements": {
      "features": {
        "activations.max": { "granted": true, "value": 5 },
        "order.advanced_patterns": { "granted": true, "value": true },
        "order.counter_reset": { "granted": false, "value": null }
      },
      "limits": {
        "activations.max": { "limit": 5, "type": "metered" }
      }
    }
  }
}

5. Record Usage

Record usage events for metered features (e.g., API calls, emails sent).

POST /api/v1/integration/usage/record

Request Body:

json
{
  "license_key": "S00Z-RGB3-73G2-DIVH",
  "metric": "api.calls",
  "quantity": 1,
  "metadata": {
    "endpoint": "/api/orders"
  }
}

Error Handling

All errors follow a consistent envelope format:

json
{
  "error": {
    "code": "LICENSE.NOT_FOUND",
    "message": "License key not found.",
    "type": "license",
    "status": 404,
    "details": {}
  },
  "meta": {
    "request_id": "uuid",
    "api_version": "1"
  }
}

Error Codes Reference

CodeHTTPWhen
LICENSE.NOT_FOUND404License key doesn't exist for this tenant
LICENSE.REVOKED403License has been revoked by administrator
AUTH.INVALID_API_KEY401Missing or invalid X-API-Key header
TENANT.STATUS.SUSPENDED403Your ValidonX tenant account is suspended
RATE_LIMIT.EXCEEDED429Too many requests — check Retry-After header
ACTIVATION.LIMIT_EXCEEDED422Maximum activations reached for this license
ENTITLEMENTS.FEATURE_DISABLED403Feature not available on this plan
ENTITLEMENTS.LIMIT_EXCEEDED422Usage limit exceeded

Rate Limits

Default: 1,000 requests per hour per API key.

Every response includes rate limit headers:

HeaderDescription
X-RateLimit-LimitRequests allowed per window
X-RateLimit-RemainingRequests remaining
X-RateLimit-ResetUnix timestamp when window resets
Retry-AfterSeconds to wait (only on 429 responses)

Best practice: Cache validation results locally for 5-15 minutes to reduce API calls.


Integration Patterns

Pattern A: Validate on Startup (Simplest)

Call validate once when your application starts. Cache the result.

php
// PHP example
$response = $validonx->validateLicense($userLicenseKey);

if (!$response['data']['valid']) {
    die('Invalid or expired license. Please renew at https://yoursite.com/renew');
}

$plan = $response['data']['license']['entitlements']['plan'] ?? 'free';
$this->enableFeaturesForPlan($plan);

Validate the license AND create an activation to track the installation.

php
// 1. Validate
$result = $validonx->validateLicense($licenseKey);
if (!$result['data']['valid']) {
    return $this->showLicenseError($result['data']['license']['status']);
}

// 2. Activate this device
$fingerprint = hash('sha256', gethostname() . php_uname('m'));
try {
    $activation = $validonx->createActivation($licenseKey, $fingerprint);
} catch (Exception $e) {
    if (str_contains($e->getMessage(), 'LIMIT_EXCEEDED')) {
        return $this->showMaxActivationsError();
    }
    throw $e;
}

// 3. Use entitlements to control features
$entitlements = $result['data']['license']['entitlements'];

Pattern C: Periodic Re-validation (Production)

Validate periodically (e.g., every 24 hours) and cache the result. Gracefully handle network failures.

php
$cached = $cache->get("license:{$licenseKey}");

if (!$cached || $cached['checked_at'] < now()->subHours(24)) {
    try {
        $result = $validonx->validateLicense($licenseKey);
        $cache->set("license:{$licenseKey}", [
            'valid' => $result['data']['valid'],
            'entitlements' => $result['data']['license']['entitlements'],
            'checked_at' => now(),
        ], ttl: 86400);
    } catch (Exception $e) {
        // Network failure: use cached result if available, or allow grace period
        if ($cached) {
            return $cached; // Use stale cache
        }
        throw $e;
    }
}

Entitlements: How to Use Them

The entitlements object on the license is a flexible key-value store. Your ValidonX administrator defines what keys exist and what they mean. Common patterns:

Plan-based feature gating

php
$plan = $entitlements['plan'] ?? 'free';

if ($plan === 'pro' || $plan === 'enterprise') {
    $this->enableAdvancedFeatures();
}

Boolean feature flags

php
if ($entitlements['smtp.oauth'] ?? false) {
    $this->showOAuthConfig();
}

Numeric limits

php
$maxActivations = $entitlements['activations.max'] ?? 1;
$maxDomains = $entitlements['domains'] ?? 1;
// -1 means unlimited

Complete PHP Integration Class

php
<?php

class ValidonXClient
{
    private string $apiKey;
    private string $baseUrl;

    public function __construct(string $apiKey, string $baseUrl = 'https://api.validonx.com/api')
    {
        $this->apiKey = $apiKey;
        $this->baseUrl = $baseUrl;
    }

    public function validateLicense(string $licenseKey): array
    {
        return $this->request('POST', "/v1/integration/licenses/{$licenseKey}/validate");
    }

    public function createActivation(string $licenseKey, string $fingerprint, array $metadata = []): array
    {
        return $this->request('POST', '/v1/integration/activations', [
            'license_key' => $licenseKey,
            'fingerprint' => $fingerprint,
            'metadata' => $metadata,
        ]);
    }

    public function validateActivation(string $activationId): array
    {
        return $this->request('POST', "/v1/integration/activations/{$activationId}/validate");
    }

    public function checkEntitlements(string $licenseKey, array $features): array
    {
        return $this->request('POST', '/v1/integration/entitlements/check', [
            'license_key' => $licenseKey,
            'features' => $features,
        ]);
    }

    public function recordUsage(string $licenseKey, string $metric, int $quantity, array $metadata = []): array
    {
        return $this->request('POST', '/v1/integration/usage/record', [
            'license_key' => $licenseKey,
            'metric' => $metric,
            'quantity' => $quantity,
            'metadata' => $metadata,
        ]);
    }

    private function request(string $method, string $path, ?array $body = null): array
    {
        $ch = curl_init("{$this->baseUrl}{$path}");
        curl_setopt_array($ch, [
            CURLOPT_CUSTOMREQUEST => $method,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => [
                "X-API-Key: {$this->apiKey}",
                'Content-Type: application/json',
                'X-Request-ID: ' . $this->uuid(),
            ],
            CURLOPT_POSTFIELDS => $body ? json_encode($body) : null,
            CURLOPT_TIMEOUT => 10,
        ]);

        $response = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        $data = json_decode($response, true);

        if ($status >= 400) {
            $code = $data['error']['code'] ?? 'UNKNOWN';
            $message = $data['error']['message'] ?? 'Request failed';
            throw new \RuntimeException("[{$code}] {$message}", $status);
        }

        return $data;
    }

    private function uuid(): string
    {
        return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
            mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff),
            mt_rand(0, 0x0fff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000,
            mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
        );
    }
}

Complete JavaScript/Node.js Integration Class

javascript
class ValidonXClient {
  constructor(apiKey, baseUrl = 'https://api.validonx.com/api') {
    this.apiKey = apiKey
    this.baseUrl = baseUrl
  }

  async validateLicense(licenseKey) {
    return this.request('POST', `/v1/integration/licenses/${licenseKey}/validate`)
  }

  async createActivation(licenseKey, fingerprint, metadata = {}) {
    return this.request('POST', '/v1/integration/activations', {
      license_key: licenseKey,
      fingerprint,
      metadata,
    })
  }

  async validateActivation(activationId) {
    return this.request('POST', `/v1/integration/activations/${activationId}/validate`)
  }

  async checkEntitlements(licenseKey, features) {
    return this.request('POST', '/v1/integration/entitlements/check', {
      license_key: licenseKey,
      features,
    })
  }

  async recordUsage(licenseKey, metric, quantity, metadata = {}) {
    return this.request('POST', '/v1/integration/usage/record', {
      license_key: licenseKey,
      metric,
      quantity,
      metadata,
    })
  }

  async request(method, path, body = null) {
    const response = await fetch(`${this.baseUrl}${path}`, {
      method,
      headers: {
        'X-API-Key': this.apiKey,
        'Content-Type': 'application/json',
        'X-Request-ID': crypto.randomUUID(),
      },
      body: body ? JSON.stringify(body) : undefined,
      signal: AbortSignal.timeout(10000),
    })

    const data = await response.json()

    if (!response.ok) {
      const code = data.error?.code || 'UNKNOWN'
      const message = data.error?.message || 'Request failed'
      throw new Error(`[${code}] ${message}`)
    }

    return data
  }
}

export default ValidonXClient

Security Best Practices

  1. Never expose your API key client-side. All ValidonX calls should happen from your server/backend.
  2. Cache validation results. Don't call validate on every page load — cache for 5-60 minutes.
  3. Handle network failures gracefully. If ValidonX is unreachable, use a cached result or a short grace period rather than immediately blocking users.
  4. Use fingerprints for activations. Hash device identifiers (hostname, MAC, hardware ID) rather than sending raw values.
  5. Store the X-Request-ID you send. It enables support debugging if something goes wrong.

Troubleshooting

SymptomLikely CauseFix
401 on every requestInvalid API keyCheck X-API-Key header value
404 on valid keyKey belongs to different tenantEnsure your API key and license key are from the same tenant
valid: false but key is activeLicense expiredCheck expires_at field
429 responsesRate limit exceededCache results, reduce call frequency
Empty entitlements object {}No entitlements set on licenseAsk your ValidonX admin to set entitlements on the license

Built by Veltara Works