Skip to content

Issuing Licenses

Learn how to create and manage licenses using the License Server API's administrative endpoints.

Prerequisites

Before issuing licenses, ensure you have:

  • License Server running and configured
  • Admin API key properly set
  • Understanding of your licensing model (tiers, limits, etc.)

Basic License Issuance

Issue a Simple License

tier, product_id, and issued_to are required. expires_at is optional — omit it (or send null) for a license that never expires:

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"
  }'

Response:

json
{
  "key": "7yChyZcfMG23Dx1sjBoLziPFrH4n-6f28",
  "status": "issued"
}

That's the entire response — the server doesn't echo back tier, product_id, issued_to, expires_at, limits, or max_activations. If you need those values afterward, keep them in your own records at issuance time, or fetch them later with GET /v1/export-license/:key or the admin-only GET /v1/admin/license/:key.

Issue a License with Usage Limits

Create a license with specific usage constraints:

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": "enterprise",
    "product_id": "your-product", 
    "issued_to": "enterprise@company.com",
    "expires_at": "2025-12-31T23:59:59Z",
    "limits": {
      "users": 500,
      "api_calls_per_day": 100000,
      "projects": 50
    },
    "max_activations": 10
  }'

License Parameters

Required Fields

FieldTypeDescription
tierstringLicense tier. Not enforced against a fixed list server-side — any non-empty string is accepted — but the published API contract's tier values are free, pro, enterprise
product_idstringProduct identifier
issued_tostringCustomer identifier (email, company name, etc.)

Optional Fields

FieldTypeDescriptionDefault
expires_atstring (ISO 8601)Expiration timestamp — omit or send null for a license that never expiresnull (never expires)
limitsobjectUsage limits — see allowed keys below{} (no limits)
max_activationsnumberMaximum concurrent activations1
notesstringInternal notesnull

License Limits

The limits object's keys are validated against a fixed allowlist — anything else is rejected with 400 {"error": "Unknown limit key: <key>"}:

json
{
  "limits": {
    "users": 100,                  // Maximum users
    "seats": 10,                   // Maximum seats
    "admins": 5,                   // Maximum admin users
    "projects": 5,                 // Number of projects
    "environments": 3,             // Number of environments
    "tenants": 1,                  // Number of tenants
    "api_calls_per_day": 10000,    // API calls per day
    "rate_limit_rps": 100,         // Requests per second
    "concurrent_sessions": 50,     // Concurrent sessions
    "features": 1                  // Feature-flag style gate
  }
}

You don't need to set all of these — include only the keys relevant to your product. There is no storage/bandwidth-related limit key, and no way to add a custom, arbitrary metric name — metric values used with Usage Tracking must be one of the keys above.

Bulk License Issuance

Using a Script

For issuing multiple licenses, create a script:

javascript
const axios = require('axios');

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

  async issueLicense(licenseData) {
    try {
      const response = await axios.post(
        `${this.apiUrl}/issue-license`,
        licenseData,
        { headers: this.headers }
      );
      return response.data;
    } catch (error) {
      throw new Error(`Failed to issue license: ${error.response?.data?.error || error.message}`);
    }
  }

  async issueBulkLicenses(licenses) {
    const results = [];
    
    for (const licenseData of licenses) {
      try {
        const license = await this.issueLicense(licenseData);
        results.push({ success: true, license, data: licenseData });
        console.log(`✓ Issued license for ${licenseData.issued_to}: ${license.key}`);
      } catch (error) {
        results.push({ success: false, error: error.message, data: licenseData });
        console.error(`✗ Failed to issue license for ${licenseData.issued_to}: ${error.message}`);
      }
    }
    
    return results;
  }
}

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

const licenses = [
  {
    tier: 'free',
    product_id: 'your-product',
    issued_to: 'customer1@company.com',
    expires_at: '2025-12-31T23:59:59Z'
  },
  {
    tier: 'pro', 
    product_id: 'your-product',
    issued_to: 'customer2@company.com',
    expires_at: '2025-12-31T23:59:59Z',
    limits: { users: 50 }
  }
  // Add more licenses...
];

issuer.issueBulkLicenses(licenses)
  .then(results => {
    const successful = results.filter(r => r.success).length;
    const failed = results.filter(r => !r.success).length;
    console.log(`\nCompleted: ${successful} successful, ${failed} failed`);
  });

CSV Import Script

Import licenses from a CSV file:

python
import csv
import requests
import os
from datetime import datetime, timezone

class BulkLicenseIssuer:
    def __init__(self, api_url, admin_key):
        self.api_url = api_url
        self.headers = {
            'Authorization': f'Bearer {admin_key}',
            'Content-Type': 'application/json'
        }
    
    def issue_license(self, license_data):
        response = requests.post(
            f'{self.api_url}/issue-license',
            json=license_data,
            headers=self.headers
        )
        response.raise_for_status()
        return response.json()
    
    def import_from_csv(self, csv_file_path):
        results = []
        
        with open(csv_file_path, 'r') as file:
            reader = csv.DictReader(file)
            
            for row in reader:
                try:
                    # Convert CSV row to license data
                    license_data = {
                        'tier': row['tier'],
                        'product_id': row['product_id'],
                        'issued_to': row['issued_to']
                    }
                    
                    # expires_at is optional -- omit or leave blank in the
                    # CSV for a license that never expires
                    if row.get('expires_at'):
                        license_data['expires_at'] = row['expires_at']

                    if row.get('max_activations'):
                        license_data['max_activations'] = int(row['max_activations'])
                    
                    # Add limits if present -- keys must come from the
                    # server's fixed allowlist (see the License Limits
                    # section above)
                    limits = {}
                    for key in ['users', 'api_calls_per_day', 'projects']:
                        if row.get(key):
                            limits[key] = int(row[key])
                    if limits:
                        license_data['limits'] = limits
                    
                    # Issue the license
                    license = self.issue_license(license_data)
                    results.append({
                        'success': True,
                        'license': license,
                        'row': row
                    })
                    print(f"✓ Issued license for {row['issued_to']}: {license['key']}")
                    
                except Exception as error:
                    results.append({
                        'success': False,
                        'error': str(error),
                        'row': row
                    })
                    print(f"✗ Failed to issue license for {row['issued_to']}: {error}")
        
        return results

# Usage
issuer = BulkLicenseIssuer(
    'https://your-license-api.com/v1',
    os.getenv('ADMIN_API_KEY')
)

# Example CSV format (expires_at may be left blank for a license that never expires):
# tier,product_id,issued_to,expires_at,max_activations,users,api_calls_per_day
# free,my-product,user1@company.com,2025-12-31T23:59:59Z,1,10,1000
# pro,my-product,user2@company.com,2025-12-31T23:59:59Z,3,50,10000

results = issuer.import_from_csv('licenses.csv')

successful = len([r for r in results if r['success']])
failed = len([r for r in results if not r['success']])
print(f"\nCompleted: {successful} successful, {failed} failed")

License Management

View License Details

Check the details of an issued license:

bash
curl "https://your-license-api.com/v1/export-license/7yChyZcfMG23Dx1sjBoLziPFrH4n-6f28"

Revoke a License

Temporarily disable 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
  }'

Reactivate a License

Re-enable a revoked 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": false
  }'

Best Practices

License Key Security

  • Never log license keys in plain text
  • Store admin API keys securely
  • Use HTTPS for all license operations
  • Implement audit logging for license operations

License Organization

  • Use consistent product_id naming
  • Include meaningful customer identifiers in issued_to
  • Document your licensing tiers and limits
  • Track license issuance in your customer database

Bulk Operations

  • Process licenses in batches to respect rate limits
  • Implement retry logic for failed operations
  • Log all operations for audit purposes
  • Validate data before issuing licenses

Monitoring

  • Track license issuance rates
  • Monitor for unusual patterns
  • Set up alerts for failed operations
  • Regular audits of active licenses

Troubleshooting

Common Issues

403 Unauthorized:

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

Rate Limit Exceeded:

  • Implement exponential backoff
  • Batch operations appropriately
  • Consider requesting rate limit increases

Invalid Parameters:

  • Check required fields are present
  • Validate date formats (ISO 8601)
  • Ensure limits are positive numbers

Error Handling Example

javascript
async function issueLicenseWithRetry(licenseData, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const license = await issuer.issueLicense(licenseData);
      return { success: true, license };
    } catch (error) {
      if (error.response?.status === 429) {
        // Rate limit - wait and retry
        const delay = Math.min(1000 * Math.pow(2, attempt), 10000);
        console.log(`Rate limited, retrying in ${delay}ms...`);
        await new Promise(resolve => setTimeout(resolve, delay));
        continue;
      }
      
      if (error.response?.status >= 500 && attempt < maxRetries) {
        // Server error - retry
        console.log(`Server error, retrying (${attempt}/${maxRetries})...`);
        continue;
      }
      
      // Don't retry client errors (4xx)
      return { success: false, error: error.message };
    }
  }
  
  return { success: false, error: 'Max retries exceeded' };
}

Next Steps