Skip to content
License Server v1.29.0 · released 30 Oct 2026release notes →
Skip to content

Authentication ​

This page is for scripting and your own servers. Day-to-day admin, such as issuing, finding and revoking licenses, is in the console: see Using the Console.

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 key — ADMIN_API_KEY is never accepted there at all. The exceptions, on every deployment mode, are the routes for whoever runs the server, which always require ADMIN_API_KEY specifically and refuse a tenant key with a 403: GET /admin/build-info, POST /admin/crash-test, GET /admin/tier-a-status, GET /self-license/status and tenant provisioning (/admin/tenants and the routes under it). If you're on the hosted tier, mentally substitute "your tenant API key" everywhere this page says ADMIN_API_KEY, except for those routes, which a hosted account never needs.

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 (/license-export, 1.21.0+), including the file variant (/license-export/file), the offline RSA-signed variant (/license-export/offline), and its public key (/public-key); also the deprecated GET /export-license/:key forms

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, lease_seconds (/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)
  • List, create and configure your products (GET/POST /admin/products, PATCH /admin/products/:id, 1.17.0+)
  • 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)
  • Send a deliberate error to check crash reporting, ADMIN_API_KEY only (/admin/crash-test, 1.15.0+)
  • Check your subscription plan/status, hosted tier only (/billing/status)
  • Start an upgrade to a paid plan, hosted tier only (/billing/checkout)
  • Rotate your own tenant API key, hosted tier only (/rotate-api-key)

Setting Up Authentication ​

Your API address

The examples use https://your-license-api.com as a stand-in for your License Server's API address. Put yours in its place:

  • Hosted account: https://license-cloud-api.casazium.com. The console's Settings → API access → API base URL shows it with /v1 already on the end, https://license-cloud-api.casazium.com/v1, so that value replaces https://your-license-api.com/v1 as a whole: don't add a second /v1. Not license-cloud.casazium.com: that's the console, and an API call sent there gets a JSON 404 naming the right address. Where an example sends $ADMIN_API_KEY, use your account's API key from the same Settings → API access section instead: a hosted account doesn't accept ADMIN_API_KEY.
  • Self-hosted: your own server's address. The self-hosted bundle answers on http://localhost:3001 on the machine running it, so http://localhost:3001/v1 replaces https://your-license-api.com/v1.

1. Get Your Admin API Key ​

(Self-hosted only — on the hosted tier, use your account's API key instead, from the console's Settings → API access.)

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": "2027-12-31T23:59:59.000Z"
  }'

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 })
    });

    // 409 means the license is already in that state; 403 can be
    // "Unauthorized" or a business rule such as "License has expired"
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${await response.text()}`);
    }

    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: '2027-12-31T23:59:59.000Z',
  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': '2027-12-31T23:59:59.000Z',
    '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": "2027-12-31T23:59:59.000Z",
    "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"
}

Admin endpoints also return 403 for business rules that have nothing to do with the key — for example {"error": "License has expired"} when un-revoking an expired license, or a quota error on the hosted tier. Check for "error": "Unauthorized" before blaming the key.

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 — it's a static comparison with no TTL. A tenant key changes only when you rotate it (POST /v1/rotate-api-key, below), after which the old one stops working at once.
  • Incorrect token format (should be Bearer <key>)

Example Error Handling ​

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

    if (!response.ok) {
      const error = await response.json().catch(() => ({}));
      if (response.status === 403 && error.error === 'Unauthorized') {
        throw new Error('Admin authentication failed. Check your API key.');
      }
      throw new Error(error.error || `Request failed with ${response.status}`);
    }

    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, each account's tenant API key is a separate mechanism from the single ADMIN_API_KEY above, and the account can rotate it itself: POST /v1/rotate-api-key, authenticated with the current tenant key, returns the new one as {"apiKey": "..."}. It's a hard cutover, the same as ADMIN_API_KEY: the old key stops working immediately, with no overlap window, so update everything that uses it straight away.

Network Security ​

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

Next Steps ​