Everything you need to put a bouncer at your app's front door. Four endpoints, one API key, zero freeloaders.
Every request needs credentials. Pass your API key via the Authorization header. No key, no entry.
curl -X POST https://your-domain.com/api/v1/licenses/validate \
-H "Authorization: Bearer kb_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{"license_key": "XXXX-XXXX-XXXX-XXXX"}'API keys are prefixed with kb_live_ for production and kb_test_ for testing.
Validation endpoints handle 1,000 requests per minute per API key. That's a lot of IDs to check.
Rate limit headers included in every response: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset
/api/v1/licenses/validateCheck a license at the door. Is this guest legit?
Request Body
{
"license_key": "XXXX-XXXX-XXXX-XXXX",
"hardware_id": "optional-device-id",
"product_id": "optional-app-id"
}Response
{
"valid": true,
"license": {
"type": "PERPETUAL",
"status": "ACTIVE",
"expires_at": null,
"features": { "tier": "pro" },
"tier": "Pro"
}
}/api/v1/licenses/activateStamp a device as approved. Let them through the rope.
Request Body
{
"license_key": "XXXX-XXXX-XXXX-XXXX",
"hardware_id": "device-unique-id",
"device_name": "Johns MacBook Pro",
"device_metadata": { "os": "macOS", "arch": "arm64" }
}Response
{
"activated": true,
"license": { "status": "ACTIVE", "activations": 1, "max_activations": 3 },
"activation": { "id": "...", "hardware_id": "...", "device_name": "..." }
}/api/v1/licenses/deactivateRevoke device access. Show them the exit.
Request Body
{
"license_key": "XXXX-XXXX-XXXX-XXXX",
"hardware_id": "device-unique-id"
}Response
{ "deactivated": true }/api/v1/licenses/checkQuick peek at the guest list. No side effects, no drama.
Parameters
Query params: ?license_key=XXXX-XXXX&hardware_id=device-idResponse
{
"valid": true,
"status": "ACTIVE",
"expires_at": null,
"features": { "tier": "pro" }
}Full example for Electron apps with hardware fingerprinting, secure storage, and offline caching.
// license.js - Main process
const { machineIdSync } = require('node-machine-id');
const Store = require('electron-store');
const store = new Store({ encryptionKey: 'your-encryption-key' });
const API_URL = 'https://your-domain.com/api/v1';
const API_KEY = 'kb_live_your_api_key';
class LicenseManager {
constructor() {
this.hardwareId = machineIdSync(true);
}
async validate(licenseKey) {
try {
const response = await fetch(`${API_URL}/licenses/validate`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
license_key: licenseKey,
hardware_id: this.hardwareId
})
});
const data = await response.json();
if (data.valid) {
// Cache license for offline use
store.set('license', {
key: licenseKey,
status: data.license.status,
tier: data.license.tier,
expiresAt: data.license.expires_at,
cachedAt: Date.now()
});
}
return data;
} catch (error) {
// Offline fallback - check cached license
return this.checkCachedLicense();
}
}
async activate(licenseKey) {
const response = await fetch(`${API_URL}/licenses/activate`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
license_key: licenseKey,
hardware_id: this.hardwareId,
device_name: require('os').hostname(),
device_metadata: {
platform: process.platform,
arch: process.arch,
version: require('os').release()
}
})
});
return response.json();
}
checkCachedLicense() {
const cached = store.get('license');
if (!cached) return { valid: false, error: 'NO_CACHED_LICENSE' };
// Allow 7 days offline grace period
const gracePeriod = 7 * 24 * 60 * 60 * 1000;
if (Date.now() - cached.cachedAt > gracePeriod) {
return { valid: false, error: 'CACHE_EXPIRED' };
}
return { valid: true, license: cached, cached: true };
}
clearLicense() {
store.delete('license');
}
}
module.exports = new LicenseManager();// main.js
const { app, BrowserWindow, ipcMain } = require('electron');
const license = require('./license');
// Handle license IPC calls from renderer
ipcMain.handle('license:validate', async (_, key) => {
return license.validate(key);
});
ipcMain.handle('license:activate', async (_, key) => {
return license.activate(key);
});
ipcMain.handle('license:check', async () => {
const cached = license.checkCachedLicense();
if (cached.valid) return cached;
// If no valid cache, user needs to enter license
return { valid: false, needsLicense: true };
});
// Check license on app start
app.whenReady().then(async () => {
const { valid } = await license.checkCachedLicense();
const mainWindow = new BrowserWindow({
// ... window options
});
if (!valid) {
mainWindow.loadFile('license.html'); // Show license entry
} else {
mainWindow.loadFile('index.html'); // Show main app
}
});// renderer.js (with contextBridge)
async function activateLicense() {
const key = document.getElementById('licenseKey').value;
const result = await window.electron.invoke('license:activate', key);
if (result.activated) {
showSuccess('License activated! Welcome aboard.');
window.location.href = 'index.html';
} else {
showError(result.error || 'Activation failed');
}
}Rust-powered license validation for Tauri apps with hardware fingerprinting.
[dependencies]
tauri = { version = "1.5", features = ["api-all"] }
reqwest = { version = "0.11", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
machine-uid = "0.3"
keyring = "2.0" # Secure credential storage// src-tauri/src/license.rs
use serde::{Deserialize, Serialize};
use reqwest::Client;
use machine_uid;
const API_URL: &str = "https://your-domain.com/api/v1";
const API_KEY: &str = "kb_live_your_api_key";
#[derive(Serialize)]
struct ValidateRequest {
license_key: String,
hardware_id: String,
}
#[derive(Deserialize, Clone)]
pub struct License {
pub tier: Option<String>,
pub status: String,
pub expires_at: Option<String>,
}
#[derive(Deserialize)]
pub struct ValidateResponse {
pub valid: bool,
pub license: Option<License>,
pub error: Option<String>,
}
pub fn get_hardware_id() -> String {
machine_uid::get().unwrap_or_else(|_| "unknown".to_string())
}
pub async fn validate_license(license_key: &str) -> Result<ValidateResponse, String> {
let client = Client::new();
let hardware_id = get_hardware_id();
let response = client
.post(format!("{}/licenses/validate", API_URL))
.header("Authorization", format!("Bearer {}", API_KEY))
.header("Content-Type", "application/json")
.json(&ValidateRequest {
license_key: license_key.to_string(),
hardware_id,
})
.send()
.await
.map_err(|e| e.to_string())?;
response.json::<ValidateResponse>()
.await
.map_err(|e| e.to_string())
}
#[derive(Serialize)]
struct ActivateRequest {
license_key: String,
hardware_id: String,
device_name: String,
}
pub async fn activate_license(license_key: &str) -> Result<ValidateResponse, String> {
let client = Client::new();
let hardware_id = get_hardware_id();
let device_name = hostname::get()
.map(|h| h.to_string_lossy().to_string())
.unwrap_or_else(|_| "Unknown".to_string());
let response = client
.post(format!("{}/licenses/activate", API_URL))
.header("Authorization", format!("Bearer {}", API_KEY))
.header("Content-Type", "application/json")
.json(&ActivateRequest {
license_key: license_key.to_string(),
hardware_id,
device_name,
})
.send()
.await
.map_err(|e| e.to_string())?;
response.json::<ValidateResponse>()
.await
.map_err(|e| e.to_string())
}// src-tauri/src/main.rs
mod license;
use tauri::State;
use std::sync::Mutex;
struct AppState {
license: Mutex<Option<license::License>>,
}
#[tauri::command]
async fn validate_license(
key: String,
state: State<'_, AppState>
) -> Result<license::ValidateResponse, String> {
let result = license::validate_license(&key).await?;
if result.valid {
if let Some(ref lic) = result.license {
*state.license.lock().unwrap() = Some(lic.clone());
}
}
Ok(result)
}
#[tauri::command]
async fn activate_license(key: String) -> Result<license::ValidateResponse, String> {
license::activate_license(&key).await
}
#[tauri::command]
fn get_hardware_id() -> String {
license::get_hardware_id()
}
fn main() {
tauri::Builder::default()
.manage(AppState {
license: Mutex::new(None),
})
.invoke_handler(tauri::generate_handler![
validate_license,
activate_license,
get_hardware_id
])
.run(tauri::generate_context!())
.expect("error running tauri app");
}// src/lib/license.ts
import { invoke } from '@tauri-apps/api/tauri';
interface LicenseResponse {
valid: boolean;
license?: {
tier: string;
status: string;
expires_at: string | null;
};
error?: string;
}
export async function validateLicense(key: string): Promise<LicenseResponse> {
return invoke('validate_license', { key });
}
export async function activateLicense(key: string): Promise<LicenseResponse> {
return invoke('activate_license', { key });
}
export async function getHardwareId(): Promise<string> {
return invoke('get_hardware_id');
}
// Usage in component
const handleActivate = async () => {
const result = await activateLicense(licenseKey);
if (result.valid) {
console.log('Licensed tier:', result.license?.tier);
// Proceed to main app
} else {
setError(result.error || 'Invalid license');
}
};For air-gapped environments, download RSA-signed license files from your dashboard. These .lic files can be validated without network access.
.lic file signature locallyconst crypto = require('crypto');
const fs = require('fs');
// Your app's public key (from dashboard)
const PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----`;
function verifyOfflineLicense(licenseFilePath, hardwareId) {
const licenseData = fs.readFileSync(licenseFilePath, 'utf8');
const [payload, signature] = licenseData.split('\n---\n');
// Verify signature
const verifier = crypto.createVerify('RSA-SHA256');
verifier.update(payload);
const isValid = verifier.verify(PUBLIC_KEY, signature, 'base64');
if (!isValid) {
return { valid: false, error: 'INVALID_SIGNATURE' };
}
// Parse and check license data
const license = JSON.parse(Buffer.from(payload, 'base64').toString());
// Check expiration
if (license.expiresAt && new Date(license.expiresAt) < new Date()) {
return { valid: false, error: 'LICENSE_EXPIRED' };
}
// Check hardware binding (if present)
if (license.hardwareId && license.hardwareId !== hardwareId) {
return { valid: false, error: 'HARDWARE_MISMATCH' };
}
return { valid: true, license };
}use rsa::{RsaPublicKey, pkcs8::DecodePublicKey, signature::Verifier};
use rsa::sha2::Sha256;
use rsa::pkcs1v15::VerifyingKey;
use base64::Engine;
const PUBLIC_KEY_PEM: &str = include_str!("../keys/public.pem");
pub fn verify_offline_license(
license_path: &str,
hardware_id: &str
) -> Result<License, String> {
let content = std::fs::read_to_string(license_path)
.map_err(|e| e.to_string())?;
let parts: Vec<&str> = content.split("\n---\n").collect();
if parts.len() != 2 {
return Err("Invalid license format".to_string());
}
let payload = parts[0];
let signature = parts[1];
// Verify RSA signature
let public_key = RsaPublicKey::from_public_key_pem(PUBLIC_KEY_PEM)
.map_err(|e| e.to_string())?;
let verifying_key = VerifyingKey::<Sha256>::new(public_key);
let sig_bytes = base64::engine::general_purpose::STANDARD
.decode(signature)
.map_err(|e| e.to_string())?;
verifying_key.verify(
payload.as_bytes(),
&rsa::pkcs1v15::Signature::try_from(sig_bytes.as_slice())
.map_err(|e| e.to_string())?
).map_err(|_| "Invalid signature")?;
// Decode and parse license
let license_json = base64::engine::general_purpose::STANDARD
.decode(payload)
.map_err(|e| e.to_string())?;
let license: License = serde_json::from_slice(&license_json)
.map_err(|e| e.to_string())?;
// Verify hardware ID
if let Some(ref bound_id) = license.hardware_id {
if bound_id != hardware_id {
return Err("Hardware mismatch".to_string());
}
}
Ok(license)
}When validation fails, the response includes an error field with one of these codes:
| Code | Description |
|---|---|
INVALID_KEY | License key not found or malformed |
LICENSE_EXPIRED | License has passed its expiration date |
LICENSE_SUSPENDED | License has been suspended by admin |
LICENSE_REVOKED | License has been permanently revoked |
ACTIVATION_LIMIT | Maximum device activations reached |
DEVICE_NOT_ACTIVATED | Hardware ID not found for this license |
INVALID_PRODUCT | License not valid for this application |
{
"valid": false,
"error": "ACTIVATION_LIMIT",
"message": "Maximum device activations (3) reached for this license"
}Quick snippets for other platforms.
import requests
import hashlib
import uuid
def get_hardware_id():
# Simple hardware fingerprint
return hashlib.sha256(str(uuid.getnode()).encode()).hexdigest()[:32]
def validate_license(license_key):
response = requests.post(
'https://your-domain.com/api/v1/licenses/validate',
headers={'Authorization': 'Bearer kb_live_your_api_key'},
json={
'license_key': license_key,
'hardware_id': get_hardware_id()
}
)
return response.json()
# Usage
result = validate_license('ABCD-1234-EFGH-5678')
if result['valid']:
print(f"Licensed: {result['license']['tier']}")
else:
print(f"Error: {result.get('error')}")package license
import (
"bytes"
"encoding/json"
"net/http"
)
const apiURL = "https://your-domain.com/api/v1"
const apiKey = "kb_live_your_api_key"
type ValidateResponse struct {
Valid bool `json:"valid"`
License struct {
Tier string `json:"tier"`
Status string `json:"status"`
} `json:"license"`
Error string `json:"error,omitempty"`
}
func Validate(licenseKey, hardwareID string) (*ValidateResponse, error) {
body, _ := json.Marshal(map[string]string{
"license_key": licenseKey,
"hardware_id": hardwareID,
})
req, _ := http.NewRequest("POST", apiURL+"/licenses/validate", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result ValidateResponse
json.NewDecoder(resp.Body).Decode(&result)
return &result, nil
}# Validate a license
curl -X POST https://your-domain.com/api/v1/licenses/validate \
-H "Authorization: Bearer kb_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{"license_key": "ABCD-1234-EFGH-5678", "hardware_id": "device-123"}'
# Activate a device
curl -X POST https://your-domain.com/api/v1/licenses/activate \
-H "Authorization: Bearer kb_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{"license_key": "ABCD-1234-EFGH-5678", "hardware_id": "device-123", "device_name": "My PC"}'