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:
- API Key — provided by your ValidonX tenant administrator (format:
vx_...) - 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_hereOptional 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 featuresAPI 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}/validateRequest (no body required):
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):
{
"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.valid—trueif the license is active and not expired. This is the primary check.data.license.status—active,suspended,expireddata.license.entitlements— flexible key-value object containing the license's granted entitlementsdata.license.expires_at— ISO 8601 timestamp, ornullfor perpetual licenses
Expired license (200, not an error):
{
"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/activationsRequest Body:
{
"license_key": "S00Z-RGB3-73G2-DIVH",
"fingerprint": "sha256-of-device-identifier",
"metadata": {
"os": "linux",
"app_version": "2.1.0",
"hostname": "server-prod-01"
}
}| Field | Required | Description |
|---|---|---|
license_key | Yes | The license key to activate |
fingerprint | Yes | Unique device identifier (hash recommended) |
metadata | No | Arbitrary JSON for your records |
Response (201):
{
"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}/validateResponse (200):
{
"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/checkRequest Body:
{
"license_key": "S00Z-RGB3-73G2-DIVH",
"features": ["activations.max", "order.advanced_patterns", "order.counter_reset"]
}Response (200):
{
"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/recordRequest Body:
{
"license_key": "S00Z-RGB3-73G2-DIVH",
"metric": "api.calls",
"quantity": 1,
"metadata": {
"endpoint": "/api/orders"
}
}Error Handling
All errors follow a consistent envelope format:
{
"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
| Code | HTTP | When |
|---|---|---|
LICENSE.NOT_FOUND | 404 | License key doesn't exist for this tenant |
LICENSE.REVOKED | 403 | License has been revoked by administrator |
AUTH.INVALID_API_KEY | 401 | Missing or invalid X-API-Key header |
TENANT.STATUS.SUSPENDED | 403 | Your ValidonX tenant account is suspended |
RATE_LIMIT.EXCEEDED | 429 | Too many requests — check Retry-After header |
ACTIVATION.LIMIT_EXCEEDED | 422 | Maximum activations reached for this license |
ENTITLEMENTS.FEATURE_DISABLED | 403 | Feature not available on this plan |
ENTITLEMENTS.LIMIT_EXCEEDED | 422 | Usage limit exceeded |
Rate Limits
Default: 1,000 requests per hour per API key.
Every response includes rate limit headers:
| Header | Description |
|---|---|
X-RateLimit-Limit | Requests allowed per window |
X-RateLimit-Remaining | Requests remaining |
X-RateLimit-Reset | Unix timestamp when window resets |
Retry-After | Seconds 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 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);Pattern B: Validate + Activate (Recommended)
Validate the license AND create an activation to track the installation.
// 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.
$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
$plan = $entitlements['plan'] ?? 'free';
if ($plan === 'pro' || $plan === 'enterprise') {
$this->enableAdvancedFeatures();
}Boolean feature flags
if ($entitlements['smtp.oauth'] ?? false) {
$this->showOAuthConfig();
}Numeric limits
$maxActivations = $entitlements['activations.max'] ?? 1;
$maxDomains = $entitlements['domains'] ?? 1;
// -1 means unlimitedComplete PHP Integration Class
<?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
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 ValidonXClientSecurity Best Practices
- Never expose your API key client-side. All ValidonX calls should happen from your server/backend.
- Cache validation results. Don't call validate on every page load — cache for 5-60 minutes.
- Handle network failures gracefully. If ValidonX is unreachable, use a cached result or a short grace period rather than immediately blocking users.
- Use fingerprints for activations. Hash device identifiers (hostname, MAC, hardware ID) rather than sending raw values.
- Store the
X-Request-IDyou send. It enables support debugging if something goes wrong.
Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
| 401 on every request | Invalid API key | Check X-API-Key header value |
| 404 on valid key | Key belongs to different tenant | Ensure your API key and license key are from the same tenant |
valid: false but key is active | License expired | Check expires_at field |
| 429 responses | Rate limit exceeded | Cache results, reduce call frequency |
Empty entitlements object {} | No entitlements set on license | Ask your ValidonX admin to set entitlements on the license |