Skip to content

Authentication

The License Server API uses Bearer token authentication for administrative endpoints, while public endpoints require no authentication.

Which key depends on how you're deployed

Everything below describes a self-hosted deployment, where ADMIN_API_KEY is genuinely the credential for every admin endpoint. On the hosted (SaaS) tier, almost all of these same endpoints instead authenticate with your tenant API keyADMIN_API_KEY is never accepted there at all. The one exception, on every deployment mode, is GET /admin/build-info, which always requires ADMIN_API_KEY specifically. If you're on the hosted tier, mentally substitute "your tenant API key" everywhere this page says ADMIN_API_KEY, except for that one route.

Authentication Levels

Public Endpoints

Most endpoints are public and require no authentication:

  • License verification (/verify-license) and validation (/validate-license)
  • License activation (/activate-license) and deactivation (/deactivate-license)
  • Usage tracking (/track-usage) and reporting (/usage-report)
  • Signature verification of a submitted license (/verify-license-file and /verify-license-file-base64)
  • Export license data (/export-license), including the file variant (/export-license/:key/file), the offline RSA-signed variant (/export-license/:key/offline), and its public key (/public-key)

Admin Endpoints (Authentication Required)

Administrative operations require a Bearer token:

  • Issue licenses (/issue-license)
  • Revoke licenses (/revoke-license)
  • Update a license's terms — expires_at, max_activations, limits (/admin/update-license-terms)
  • Delete licenses (/delete-license)
  • List all licenses (/list-licenses)
  • Update a license's internal notes (/admin/update-notes)
  • View admin statistics (/admin/stats)
  • View recent activations across all licenses (/recent-activations)
  • List activations for a specific license key (/list-activations/:key) — returns per-device instance IDs and timestamps, sensitive enough that it requires admin auth
  • Look up a single license's full record (/admin/license/:key)
  • Reissue an activation token (/admin/reissue-token)
  • Force-free a seat by instance_id, without its token (/admin/deactivate-by-instance-id)
  • Reset a license's tracked usage (/admin/reset-usage)
  • View build/watermark info (/admin/build-info)
  • Check your subscription plan/status, hosted tier only (/billing/status)
  • Start an upgrade to a paid plan, hosted tier only (/billing/checkout)

Setting Up Authentication

1. Get Your Admin API Key

(Self-hosted only — on the hosted tier, use your tenant API key instead, from your console dashboard.)

The admin API key is configured via environment variable:

bash
export ADMIN_API_KEY="your-secure-admin-api-key-here"

Security Best Practices

  • Use a strong, randomly generated key (minimum 32 characters)
  • Store the key securely (environment variables, secrets manager)
  • Rotate the key regularly
  • Never log or expose the key in client-side code

2. Making Authenticated Requests

Include the Bearer token in the Authorization header:

bash
curl -X POST https://your-license-api.com/v1/issue-license \
  -H "Authorization: Bearer your-admin-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "tier": "pro",
    "product_id": "your-product",
    "issued_to": "customer@company.com",
    "expires_at": "2025-12-31T23:59:59Z"
  }'

Client Examples

JavaScript/Node.js

javascript
class LicenseAdminClient {
  constructor(baseURL, adminKey) {
    this.baseURL = baseURL;
    this.headers = {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${adminKey}`
    };
  }

  async issueLicense(licenseData) {
    const response = await fetch(`${this.baseURL}/issue-license`, {
      method: 'POST',
      headers: this.headers,
      body: JSON.stringify(licenseData)
    });
    
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${await response.text()}`);
    }
    
    return response.json();
  }

  async revokeLicense(key, revoked = true) {
    const response = await fetch(`${this.baseURL}/revoke-license`, {
      method: 'POST', 
      headers: this.headers,
      body: JSON.stringify({ key, revoked })
    });
    
    return response.json();
  }
}

// Usage
const adminClient = new LicenseAdminClient(
  'https://your-license-api.com/v1',
  process.env.ADMIN_API_KEY
);

const license = await adminClient.issueLicense({
  tier: 'pro',
  product_id: 'your-product',
  issued_to: 'customer@example.com',
  expires_at: '2025-12-31T23:59:59Z',
  limits: { users: 100 }
});

Python

python
import os
import requests
from typing import Dict, Any

class LicenseAdminClient:
    def __init__(self, base_url: str, admin_key: str = None):
        self.base_url = base_url
        self.admin_key = admin_key or os.getenv('ADMIN_API_KEY')
        
        if not self.admin_key:
            raise ValueError("Admin API key is required")
        
        self.headers = {
            'Content-Type': 'application/json',
            'Authorization': f'Bearer {self.admin_key}'
        }
    
    def issue_license(self, license_data: Dict[str, Any]) -> Dict[str, Any]:
        response = requests.post(
            f'{self.base_url}/issue-license',
            json=license_data,
            headers=self.headers
        )
        response.raise_for_status()
        return response.json()
    
    def revoke_license(self, key: str, revoked: bool = True) -> Dict[str, Any]:
        response = requests.post(
            f'{self.base_url}/revoke-license',
            json={'key': key, 'revoked': revoked},
            headers=self.headers
        )
        response.raise_for_status() 
        return response.json()

# Usage
admin_client = LicenseAdminClient('https://your-license-api.com/v1')

license = admin_client.issue_license({
    'tier': 'pro',
    'product_id': 'your-product', 
    'issued_to': 'customer@example.com',
    'expires_at': '2025-12-31T23:59:59Z',
    'limits': {'users': 100}
})

cURL Examples

Issue a License

bash
curl -X POST https://your-license-api.com/v1/issue-license \
  -H "Authorization: Bearer $ADMIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tier": "pro",
    "product_id": "your-product",
    "issued_to": "customer@company.com", 
    "expires_at": "2025-12-31T23:59:59Z",
    "limits": {
      "users": 100,
      "api_calls_per_day": 10000
    },
    "max_activations": 5
  }'

Revoke a License

bash
curl -X POST https://your-license-api.com/v1/revoke-license \
  -H "Authorization: Bearer $ADMIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "7yChyZcfMG23Dx1sjBoLziPFrH4n-6f28",
    "revoked": true
  }'

Get Admin Statistics

bash
curl -H "Authorization: Bearer $ADMIN_API_KEY" \
     https://your-license-api.com/v1/admin/stats

Error Handling

403 Forbidden

When authentication fails, you'll receive a 403 status with an error message:

json
{
  "error": "Unauthorized"
}

Common causes:

  • Missing Authorization header
  • The key doesn't match — the server's configured ADMIN_API_KEY on self-hosted, or your own tenant API key on the hosted tier (see the warning at the top of this page). Either way, the key itself never expires on its own — there's no rotation/TTL mechanism, it's a static comparison.
  • Incorrect token format (should be Bearer <key>)

Example Error Handling

javascript
try {
  const response = await fetch('/issue-license', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${adminKey}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(licenseData)
  });

  if (response.status === 403) {
    throw new Error('Admin authentication failed. Check your API key.');
  }
  
  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.error || 'Request failed');
  }
  
  return await response.json();
} catch (error) {
  console.error('License operation failed:', error.message);
  throw error;
}

Security Considerations

Token Storage

  • Server-side: Store in environment variables or secure secret managers
  • Never store admin tokens in:
    • Client-side JavaScript
    • Version control systems
    • Log files
    • URLs or query parameters

Token Rotation

  • Use different keys for different environments (dev/staging/production)
  • ADMIN_API_KEY is a single static value with no built-in versioning or TTL — rotating it means setting a new value and restarting the server; the old key stops working the moment the new deployment is live, with no overlap window where both are accepted. Plan rotations around a brief window where in-flight admin requests using the old key will fail, rather than "without downtime."
  • On a hosted deployment, individual tenant API keys are a separate mechanism from the single ADMIN_API_KEY above, and do support live rotation with no downtime — a platform-operator operation, not something a tenant does themselves.

Network Security

  • Always use HTTPS in production
  • Consider IP allowlisting for admin operations
  • Implement request signing for additional security if needed

Next Steps