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-fileand/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 deprecatedGET /export-license/:keyforms
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_KEYonly (/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/v1already on the end,https://license-cloud-api.casazium.com/v1, so that value replaceshttps://your-license-api.com/v1as a whole: don't add a second/v1. Notlicense-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 acceptADMIN_API_KEY. - Self-hosted: your own server's address. The self-hosted bundle answers on
http://localhost:3001on the machine running it, sohttp://localhost:3001/v1replaceshttps://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:
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:
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
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
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
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
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
curl -H "Authorization: Bearer $ADMIN_API_KEY" \
https://your-license-api.com/v1/admin/statsError Handling
403 Forbidden
When authentication fails, you'll receive a 403 status with an error message:
{
"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
Authorizationheader - The key doesn't match — the server's configured
ADMIN_API_KEYon 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
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_KEYis 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_KEYabove, 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 asADMIN_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
- Learn about rate limits to understand request quotas
- Security policy — how to report a vulnerability privately
- Explore the Integration Guide for implementation examples
- Check out Admin Operations for license management workflows