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
- Checkout: The application sends a
POST /key/checkoutrequest to lease a seat. The API returns a uniquesessionId. - Heartbeat: The application sends a
POST /key/heartbeatperiodically to extend the lease. - Expiry: If a client crashes, the heartbeat stops. Keymint automatically reclaims the seat after the lease duration expires.
- Checkin: When the application exits normally, it calls
POST /key/checkinto release the seat.
Configuration Parameters
| Parameter | Recommended Value | Description |
|---|---|---|
| Heartbeat Interval | 120s | Frequency at which the application sends heartbeat requests. |
| Lease Duration | 300s | The time window a seat remains locked before automatic reclamation. |
| Grace Period | 15s | Server-side buffer window before reclaiming an expired lease. |
Integration Workflow
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:
| Field | Value |
|---|---|
timestamp | The session's current nextNonce (from the last checkout/heartbeat response) |
signature | HMAC-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.
// 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();