API Reference

The Bouncer's Playbook

Everything you need to put a bouncer at your app's front door. Four endpoints, one API key, zero freeloaders.

Authentication

Every request needs credentials. Pass your API key via the Authorization header. No key, no entry.

bash
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.

Rate Limits

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

Endpoints

POST/api/v1/licenses/validate

Check a license at the door. Is this guest legit?

Request Body

json
{
  "license_key": "XXXX-XXXX-XXXX-XXXX",
  "hardware_id": "optional-device-id",
  "product_id": "optional-app-id"
}

Response

json
{
  "valid": true,
  "license": {
    "type": "PERPETUAL",
    "status": "ACTIVE",
    "expires_at": null,
    "features": { "tier": "pro" },
    "tier": "Pro"
  }
}
POST/api/v1/licenses/activate

Stamp a device as approved. Let them through the rope.

Request Body

json
{
  "license_key": "XXXX-XXXX-XXXX-XXXX",
  "hardware_id": "device-unique-id",
  "device_name": "Johns MacBook Pro",
  "device_metadata": { "os": "macOS", "arch": "arm64" }
}

Response

json
{
  "activated": true,
  "license": { "status": "ACTIVE", "activations": 1, "max_activations": 3 },
  "activation": { "id": "...", "hardware_id": "...", "device_name": "..." }
}
POST/api/v1/licenses/deactivate

Revoke device access. Show them the exit.

Request Body

json
{
  "license_key": "XXXX-XXXX-XXXX-XXXX",
  "hardware_id": "device-unique-id"
}

Response

json
{ "deactivated": true }
GET/api/v1/licenses/check

Quick peek at the guest list. No side effects, no drama.

Parameters

json
Query params: ?license_key=XXXX-XXXX&hardware_id=device-id

Response

json
{
  "valid": true,
  "status": "ACTIVE",
  "expires_at": null,
  "features": { "tier": "pro" }
}

Electron Integration

Full example for Electron apps with hardware fingerprinting, secure storage, and offline caching.

License Manager Module

javascript
// 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 Process Setup

javascript
// 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 Process

javascript
// 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');
  }
}

Tauri Integration

Rust-powered license validation for Tauri apps with hardware fingerprinting.

Cargo.toml Dependencies

toml
[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

Rust License Module

rust
// 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())
}

Tauri Commands

rust
// 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");
}

Frontend (TypeScript)

typescript
// 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');
  }
};

Offline Validation

For air-gapped environments, download RSA-signed license files from your dashboard. These .lic files can be validated without network access.

How It Works

  1. Download the offline license file from your KeyBouncer dashboard
  2. Ship your app's public key (RSA) with your application
  3. Load and verify the .lic file signature locally
  4. Check expiration and hardware ID without any network calls

Node.js / Electron

javascript
const 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 };
}

Rust / Tauri

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

Error Codes

When validation fails, the response includes an error field with one of these codes:

CodeDescription
INVALID_KEYLicense key not found or malformed
LICENSE_EXPIREDLicense has passed its expiration date
LICENSE_SUSPENDEDLicense has been suspended by admin
LICENSE_REVOKEDLicense has been permanently revoked
ACTIVATION_LIMITMaximum device activations reached
DEVICE_NOT_ACTIVATEDHardware ID not found for this license
INVALID_PRODUCTLicense not valid for this application
json
{
  "valid": false,
  "error": "ACTIVATION_LIMIT",
  "message": "Maximum device activations (3) reached for this license"
}

More Examples

Quick snippets for other platforms.

Python

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

Go

go
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
}

cURL

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

Ready to lock it down?

Create a free account and put your first bouncer on the door.

Get Started