Rate Limits
The License Server API implements intelligent rate limiting to protect the service while allowing legitimate usage patterns. Different endpoint categories have different rate limits based on their expected usage frequency.
Rate Limit Categories
High Frequency - Validation Endpoints
1000 requests / 15 minutes
These endpoints are designed for high-frequency real-time validation:
- POST
/v1/verify-license- Verify license validity - POST
/v1/validate-license- Validate license for instance - POST
/v1/track-usage- Report usage metrics - POST
/v1/usage-report- Get usage history - POST
/v1/check-update- License-gated software update check (Software Distribution)
Use case: Real-time license checks in your application, usage tracking, monitoring.
Moderate Frequency - Activation Endpoints
300 requests / 15 minutes
These endpoints handle license activation and file operations:
- POST
/v1/activate-license- Activate license for instance - POST
/v1/deactivate-license- Free a seat so it can be activated elsewhere - POST
/v1/verify-license-file- Verify license (JSON body, despite the name — not a file upload) - POST
/v1/verify-license-file-base64- Verify Base64 license file
Use case: License activation during application startup, moving a seat between machines, license file verification.
Additional per-key limit on activation
activate-license and deactivate-license carry a second, stricter limit on top of the 300/15min per-IP one above: 20 requests / 15 minutes, keyed per license key (not per IP). This exists because the license key alone authorizes activation — without it, one key could be hammered with activation guesses from many different IPs. If you're activating many instances of the same key in a short window (e.g. a bulk rollout), you can hit this limit well before the per-IP one.
Standard Frequency - Public Endpoints
500 requests / 15 minutes
Public information endpoints with generous limits:
- GET
/v1/export-license/{key}- Get license information - GET
/v1/export-license/{key}/file- Download license file - GET
/v1/export-license/{key}/offline- Get an RSA-signed license, verifiable offline with only a public key - GET
/v1/public-key- Get the RSA public key used for offline verification
Use case: Exporting license data, downloading license files, offline license verification.
Low Frequency - Management Endpoints
100 requests / 15 minutes (Authentication Required)
Sensitive license management operations:
- POST
/v1/issue-license- Create new license - POST
/v1/revoke-license- Revoke/reactivate license - POST
/v1/admin/update-license-terms- Update a license's expires_at, max_activations, or limits - DELETE
/v1/delete-license- Delete license - POST
/v1/register-release- Register a software release (Software Distribution) - POST
/v1/unpublish-release- Unpublish a software release (Software Distribution)
Use case: Issuing, revoking, and deleting licenses; registering and unpublishing software releases.
Admin Reporting Endpoints
300 requests / 15 minutes (Authentication Required) — the same numeric limit as the Activation tier above, not lower despite coming after "Low Frequency" on this page; the two just aren't on the same ordinal scale (this one is configurable, the others aren't).
Administrative reporting operations. Unlike every other tier on this page, this limit is configurable via the ADMIN_RATE_LIMIT_MAX environment variable — 300 is just the default:
- GET
/v1/list-licenses- List all licenses - GET
/v1/list-releases- List your registered software releases (Software Distribution) - GET
/v1/release/{id}- Fetch a single software release, including its signature (Software Distribution) - GET
/v1/admin/stats- Get admin statistics - GET
/v1/recent-activations- Get recent activations - GET
/v1/list-activations/{key}- List activations for a specific license key - GET
/v1/admin/license/{key}- Look up a single license's full record - POST
/v1/admin/update-notes- Update a license's internal notes - POST
/v1/admin/reissue-token- Reissue an activation token - POST
/v1/admin/deactivate-by-instance-id- Force-free a seat byinstance_id, without its token - POST
/v1/admin/reset-usage- Reset a license's tracked usage - GET
/v1/admin/build-info- View build/watermark info - GET
/v1/billing/status- Check the calling tenant's subscription plan/status (hosted tier) - POST
/v1/billing/checkout- Start an upgrade to a paid plan (hosted tier) - POST
/v1/rotate-api-key- Rotate the calling tenant's own API key (hosted tier)
Use case: Administration dashboards, periodic reporting, auditing per-license device activity.
Rate Limit Headers
Every API response includes rate limit information in the headers:
HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1640995200| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed in the time window |
X-RateLimit-Remaining | Requests remaining in current window |
X-RateLimit-Reset | Unix timestamp when the window resets |
Handling Rate Limits
When You Hit the Limit
When you exceed the rate limit, you'll receive a 429 Too Many Requests response:
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1640995200
Retry-After: 60
{
"statusCode": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded. Try again in 60 seconds.",
"retryAfter": 60
}Best Practices
1. Respect Rate Limits
Always check rate limit headers and implement backoff:
async function makeAPIRequest(url, options) {
const response = await fetch(url, options);
// Check rate limit headers
const remaining = parseInt(response.headers.get('X-RateLimit-Remaining'));
const resetTime = parseInt(response.headers.get('X-RateLimit-Reset'));
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After')) * 1000;
console.warn(`Rate limit exceeded. Retrying in ${retryAfter}ms`);
await new Promise(resolve => setTimeout(resolve, retryAfter));
return makeAPIRequest(url, options); // Retry
}
// Warn when approaching limit
if (remaining < 10) {
console.warn(`Approaching rate limit: ${remaining} requests remaining`);
}
return response;
}2. Implement Exponential Backoff
class LicenseClient {
constructor(baseURL) {
this.baseURL = baseURL;
this.maxRetries = 3;
}
async request(endpoint, options, retryCount = 0) {
try {
const response = await fetch(`${this.baseURL}${endpoint}`, options);
if (response.status === 429 && retryCount < this.maxRetries) {
const retryAfter = parseInt(response.headers.get('Retry-After')) || 1;
const delay = Math.min(1000 * Math.pow(2, retryCount), retryAfter * 1000);
console.log(`Rate limited. Retrying in ${delay}ms (attempt ${retryCount + 1})`);
await new Promise(resolve => setTimeout(resolve, delay));
return this.request(endpoint, options, retryCount + 1);
}
return response;
} catch (error) {
if (retryCount < this.maxRetries) {
const delay = 1000 * Math.pow(2, retryCount);
await new Promise(resolve => setTimeout(resolve, delay));
return this.request(endpoint, options, retryCount + 1);
}
throw error;
}
}
}3. Cache License Verification Results
class CachedLicenseClient {
constructor(baseURL, cacheTimeout = 300000) { // 5 minutes
this.baseURL = baseURL;
this.cache = new Map();
this.cacheTimeout = cacheTimeout;
}
async verifyLicense(key) {
const cacheKey = `verify:${key}`;
const cached = this.cache.get(cacheKey);
// Return cached result if still valid
if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
return cached.result;
}
// Make API request
const response = await fetch(`${this.baseURL}/verify-license`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ key })
});
const result = await response.json();
// Cache successful results
if (response.ok && result.valid) {
this.cache.set(cacheKey, {
result,
timestamp: Date.now()
});
}
return result;
}
}4. Batch Operations When Possible
Instead of making many individual requests, batch operations:
// ❌ Bad: Multiple individual requests
for (const key of licenseKeys) {
await verifyLicense(key);
}
// ✅ Good: Batch verification (if your app supports it)
const results = await Promise.all(
licenseKeys.map(key => verifyLicense(key))
);
// ✅ Better: Use rate-aware batching
async function batchVerifyLicenses(keys, batchSize = 10, delayBetweenBatches = 1000) {
const results = [];
for (let i = 0; i < keys.length; i += batchSize) {
const batch = keys.slice(i, i + batchSize);
const batchResults = await Promise.all(
batch.map(key => verifyLicense(key))
);
results.push(...batchResults);
// Add delay between batches to stay within rate limits
if (i + batchSize < keys.length) {
await new Promise(resolve => setTimeout(resolve, delayBetweenBatches));
}
}
return results;
}Rate Limiting by IP
Rate limits are applied per IP address on every tier, with one exception: activate-license and deactivate-license are additionally limited per license key (see the warning above). This means:
- Each client IP gets its own rate limit bucket
- Multiple users behind the same NAT/proxy share limits
- Load balancers should preserve client IP addresses
Handling Shared IP Scenarios
If multiple users share an IP (corporate NAT, shared hosting), consider:
- User-based rate limiting (custom implementation)
- API key based limits (for authenticated users)
- Request higher limits for shared environments
Monitoring Rate Limits
Client-Side Monitoring
Track your rate limit usage:
class RateLimitMonitor {
constructor() {
this.limits = new Map();
}
trackResponse(endpoint, response) {
const limit = parseInt(response.headers.get('X-RateLimit-Limit'));
const remaining = parseInt(response.headers.get('X-RateLimit-Remaining'));
const reset = parseInt(response.headers.get('X-RateLimit-Reset'));
this.limits.set(endpoint, {
limit,
remaining,
reset,
usagePercent: ((limit - remaining) / limit * 100).toFixed(1)
});
// Log high usage
if (remaining < limit * 0.1) { // Less than 10% remaining
console.warn(`High rate limit usage for ${endpoint}: ${remaining}/${limit} remaining`);
}
}
getLimitStatus(endpoint) {
return this.limits.get(endpoint);
}
getAllLimitStatus() {
return Object.fromEntries(this.limits);
}
}Server-Side Monitoring
If you run the License Server, monitor rate limit metrics:
- Requests per endpoint category
- Rate limit violations by IP
- Peak usage times
- Client retry patterns
Fixed Rate Limits
Every tier's limit is a fixed value set in the server implementation, except the Admin-Info tier, which is configurable via the ADMIN_RATE_LIMIT_MAX environment variable (default 300 requests / 15 minutes).
Need Higher Limits?
If your application requires higher rate limits:
- Optimize your usage - Cache results, batch requests
- Contact support - For enterprise license limits
- Self-host - Deploy your own instance with custom limits
- Use multiple endpoints - Distribute load across endpoint categories
Next Steps
- Learn about authentication for admin endpoints
- Explore integration patterns that respect rate limits
- Check out caching strategies
