Floating Licenses

Enable concurrency-limited, real-time license leasing for distributed systems, desktop applications, and CLI tools.

Floating licensing allocates a dynamic pool of seats to a customer. Devices check out seats from Keymint, extend them via heartbeats, and release them upon application shutdown.

Core Mechanics

  1. Checkout: The application sends a POST /key/checkout request to lease a seat. The API returns a unique sessionId.
  2. Heartbeat: The application sends a POST /key/heartbeat periodically to extend the lease.
  3. Expiry: If a client crashes, the heartbeat stops. Keymint automatically reclaims the seat after the lease duration expires.
  4. Checkin: When the application exits normally, it calls POST /key/checkin to release the seat.

Configuration Parameters

ParameterRecommended ValueDescription
Heartbeat Interval120sFrequency at which the application sends heartbeat requests.
Lease Duration300sThe time window a seat remains locked before automatic reclamation.
Grace Period15sServer-side buffer window before reclaiming an expired lease.

Integration Workflow

mermaid
Rendering diagram...

Session Extension (Re-Checkout)

The first POST /key/checkout for a hostId creates a new session and requires no signature. Re-checking out a hostId that still has an active session is treated as a session extension and now requires proof that the caller possesses the session secret:

FieldValue
timestampThe session's current nextNonce (from the last checkout/heartbeat response)
signatureHMAC-SHA256(sessionSecret, "${sessionId}:${timestamp}")

This is the same challenge-response scheme as heartbeat and checkin. It prevents a device behind a shared NAT from rotating another device's nonce and taking over its session. An unsigned or invalid extension is rejected with 401.

typescript
// Extending an ACTIVE session (e.g. app restarted before the lease expired)
const signature = KeyMint.generateSessionSignature(
  session.sessionId,   // from the original checkout response
  session.nextNonce,   // the CURRENT nonce — rotate after every response
  session.sessionSecret
);

const extended = await keymint.floatingCheckout({
  productId,
  licenseKey,
  hostId,
  timestamp: session.nextNonce,
  signature
});

// The response rotates the nonce and secret — store both for the next call.
session.nextNonce = extended.nextNonce;
session.sessionSecret = extended.sessionSecret;

If the previous lease has already expired, checkout behaves like a fresh checkout (no signature required). Only active-session extensions enforce proof of possession.

Session Management

You can monitor and manage active floating sessions in the Keymint dashboard:

  • Live Count: View real-time active lease ratios (e.g., 3/10 seats active) under your license key.
  • Force-Revocation: Manually terminate active database sessions to free up seats for other devices if a client crashes or fails to check in.

Integration Code Examples

Below is a complete implementation pattern showing how to lease, extend, and release floating license seats using the Keymint SDKs.

import { KeyMint } from 'keymint';

const keymint = new KeyMint('YOUR_API_KEY');
const productId = 'prod_Nx8K2mLpQ4rVtW9sBc';
const licenseKey = 'A8E2K-9F1BC-3D4GH-7J2KM';

// 1. Resolve a stable host identifier for this device
const hostId = KeyMint.getOrCreateInstallationId();

async function manageFloatingLicense() {
  try {
    // 2. Checkout a seat session
    const session = await keymint.floatingCheckout({
      productId,
      licenseKey,
      hostId
    });

    console.log(`License leased. Session ID: ${session.sessionId}`);
    let currentNonce = session.nextNonce;

    // 3. Set up a periodic heartbeat loop based on the returned interval
    const heartbeatIntervalMs = session.heartbeatInterval * 1000;
    const intervalId = setInterval(async () => {
      try {
        // Generate the HMAC signature using the rotating nonce
        const signature = KeyMint.generateSessionSignature(
          session.sessionId,
          currentNonce,
          session.sessionSecret
        );

        const heartbeatRes = await keymint.floatingHeartbeat({
          productId,
          licenseKey,
          sessionId: session.sessionId,
          timestamp: currentNonce,
          signature
        });

        // Rotate the nonce for the next heartbeat challenge
        currentNonce = heartbeatRes.nextNonce;
        console.log('Heartbeat extended successfully.');
      } catch (err) {
        console.error('Heartbeat failed:', err.message);
        // Implement backup retry/grace logic here (e.g. warn user or restrict features)
      }
    }, heartbeatIntervalMs);

    // 4. Register a clean shutdown hook to check in the seat
    process.on('SIGTERM', async () => {
      clearInterval(intervalId);
      try {
        const signature = KeyMint.generateSessionSignature(
          session.sessionId,
          currentNonce,
          session.sessionSecret
        );
        await keymint.floatingCheckin({
          productId,
          licenseKey,
          sessionId: session.sessionId,
          timestamp: currentNonce,
          signature
        });
        console.log('Session checked in. Seat released.');
        process.exit(0);
      } catch (err) {
        console.error('Graceful checkin failed:', err.message);
        process.exit(1);
      }
    });

  } catch (error) {
    console.error('Checkout failed (no available seats or invalid license):', error.message);
  }
}

manageFloatingLicense();