Licensing API
The Licensing API lets you create license policies, issue keys, validate them at runtime, manage seats and devices, and automate key issuance from payment webhooks. It supports online validation, offline JWT fallback, and hybrid modes.
Two audiences, two auth levels:
| Audience | Auth | Endpoints |
|---|---|---|
| Your app / SDK (runtime) | No Bearer token needed | /validate, /activate, /deactivate, /usage, /revocation-list |
| Admin / Console (management) | Bearer token + permission | Everything else |
All endpoints require the x-tenant header.
Policies
Policies define what a license grants — features, seats, devices, expiry, key format, and validation mode.
/api/licensing/policiesRequires: manage:license-policiesList all policies for the current tenant. Supports pagination and search.
Query parameters
| Param | Type | Description |
|---|---|---|
page | number | Page number (default: 1) |
limit | number | Items per page (default: 20) |
search | string | Filter by name or slug |
Success response
{
"success": true,
"data": [
{
"id": "pol_abc123",
"name": "Pro Plan",
"slug": "pro-plan",
"validationMode": "HYBRID",
"isActive": true,
"offlineGraceDays": 7,
"revocationTtlMin": 60,
"dimensions": {
"seats": { "enabled": true, "defaultMax": 5 },
"devices": { "enabled": true, "defaultMax": 3 },
"expiry": { "enabled": true, "defaultDays": 365 },
"features": { "enabled": true, "available": ["analytics", "export", "api-access"] },
"keyFormat": {
"prefix": "VIG",
"segments": 4,
"segmentLength": 4,
"separator": "-",
"charset": "BASE32"
}
},
"createdAt": "2026-01-15T10:00:00Z"
}
],
"pagination": { "page": 1, "limit": 20, "total": 3, "pages": 1 }
}/api/licensing/policiesRequires: manage:license-policiesCreate a new license policy.
Request body
{
"name": "Pro Plan",
"slug": "pro-plan",
"validationMode": "HYBRID",
"dimensions": {
"seats": { "enabled": true, "defaultMax": 5 },
"devices": { "enabled": true, "defaultMax": 3 },
"expiry": { "enabled": true, "defaultDays": 365 },
"features": { "enabled": true, "available": ["analytics", "export"] },
"keyFormat": {
"prefix": "VIG",
"segments": 4,
"segmentLength": 4,
"separator": "-",
"charset": "BASE32"
}
}
}| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Display name |
slug | string | Yes | Unique slug (e.g. pro-plan) |
validationMode | ONLINE | HYBRID | OFFLINE | No | Default: HYBRID |
dimensions | object | No | Seats, devices, features, expiry, key format config |
offlineGraceDays | number | No | Days a key stays valid offline (default: 7) |
revocationTtlMin | number | No | Minutes before revocation propagates (default: 60) |
/api/licensing/policies/[id]Requires: manage:license-policiesGet a single policy by ID.
/api/licensing/policies/[id]Requires: manage:license-policiesUpdate a policy. Only provided fields are changed.
/api/licensing/policies/[id]Requires: manage:license-policiesDelete a policy. Fails if keys are still issued under it.
Keys
Keys are issued against a policy and granted to a licensee (user, org, or device).
/api/licensing/keysRequires: manage:license-keysList all keys. Filterable by status and policy.
Query parameters
| Param | Type | Description |
|---|---|---|
page | number | Page number |
limit | number | Items per page |
policyId | string | Filter by policy |
status | ACTIVE | SUSPENDED | REVOKED | EXPIRED | Filter by status |
/api/licensing/keysRequires: manage:license-keysIssue a new license key.
Request body
{
"policyId": "pol_abc123",
"licenseeType": "USER",
"licenseeId": "user_xyz789",
"notes": "Issued via Stripe checkout"
}Response includes the full key object with key (the license string) and jwtToken (for offline validation).
/api/licensing/keys/[id]Requires: manage:license-keysGet a single key by ID, including related policy, seats, and devices.
/api/licensing/keys/[id]/suspendRequires: manage:license-keysSuspend a key. It can be reactivated later.
/api/licensing/keys/[id]/revokeRequires: manage:license-keysPermanently revoke a key.
/api/licensing/keys/[id]/reissueRequires: manage:license-keysReissue a key with updated entitlements. The old key is revoked and a new one is generated.
Optional body
{
"features": ["analytics", "export", "api-access"],
"seatMax": 10,
"deviceMax": 5,
"expiresAt": "2027-03-15T00:00:00Z"
}Seats
/api/licensing/keys/[id]/seatsRequires: manage:license-keysList all seats (assigned users) for a key.
/api/licensing/keys/[id]/seatsRequires: manage:license-keysAssign a seat to a user. Fails if the seat limit is reached.
{ "userId": "user_abc" }/api/licensing/keys/[id]/seats/[userId]Requires: manage:license-keysRelease a seat from a user.
Floating Licenses
Floating (concurrent) licensing allows a limited number of seats to be shared across a larger pool of users. Users check out a seat when they start a session and check it back in when they finish. Seats that are not checked in expire automatically after the lease period.
/api/licensing/checkoutCheck out a floating seat. Assigns a lease to the user for the duration configured on the policy. Public endpoint — no Bearer token required.
Request body
{
"key": "VIG-A8BC-D3EF-G4HJ-K5LM",
"userId": "user_abc"
}Success response
{
"success": true,
"data": {
"id": "seat_xyz",
"userId": "user_abc",
"leaseExpiresAt": "2026-03-16T14:30:00Z",
"checkedOutAt": "2026-03-16T13:30:00Z"
}
}Returns 409 if the seat limit is reached.
/api/licensing/checkinCheck in (release) a floating seat. The seat becomes available for other users immediately. Public endpoint — no Bearer token required.
Request body
{
"key": "VIG-A8BC-D3EF-G4HJ-K5LM",
"userId": "user_abc"
}/api/licensing/heartbeatExtend a floating lease. Call this periodically from your application to keep the seat active. Public endpoint — no Bearer token required.
Request body
{
"key": "VIG-A8BC-D3EF-G4HJ-K5LM",
"userId": "user_abc"
}Success response
{
"success": true,
"data": {
"leaseExpiresAt": "2026-03-16T15:00:00Z"
}
}Admin: Floating Seats Monitor
/api/licensing/floatingRequires: manage:license-keysList all active floating seats across all keys for the tenant. Keys are masked in the response.
Success response
{
"success": true,
"data": [
{
"id": "seat_xyz",
"userId": "user_abc",
"keyMasked": "VIG-****-****-****-K5LM",
"keyId": "key_abc123",
"checkedOutAt": "2026-03-16T13:30:00Z",
"leaseExpiresAt": "2026-03-16T14:30:00Z"
}
]
}/api/licensing/floating?seatId=xxxRequires: manage:license-keysForce-release a floating seat. Use this to free up stuck seats without waiting for the lease to expire.
Query parameters
| Param | Type | Description |
|---|---|---|
seatId | string | The ID of the floating seat to release |
Devices
/api/licensing/keys/[id]/devicesRequires: manage:license-keysList all activated devices for a key.
/api/licensing/keys/[id]/devices/[deviceId]Requires: manage:license-keysRemove a device from a key.
Device Registry
/api/licensing/devicesRequires: manage:license-keysList all devices across all license keys for the tenant. Supports pagination, search, and filtering by activation method.
Query parameters
| Param | Type | Description |
|---|---|---|
page | number | Page number (default: 1) |
limit | number | Items per page (default: 20) |
search | string | Filter by fingerprint or device name |
method | ONLINE | OFFLINE | Filter by activation method |
Success response
{
"success": true,
"data": [
{
"id": "dev_abc123",
"fingerprint": "a1b2c3d4e5f6",
"name": "John's MacBook Pro",
"method": "ONLINE",
"keyId": "key_xyz789",
"keyMasked": "VIG-****-****-****-K5LM",
"activatedAt": "2026-02-10T08:00:00Z",
"lastSeenAt": "2026-03-16T12:00:00Z"
}
],
"pagination": { "page": 1, "limit": 20, "total": 45, "pages": 3 }
}Validation (Public)
These endpoints are called by your application at runtime. No Bearer token is required.
/api/licensing/validateValidate a license key online. Returns validity, features, seat/device counts, and expiry.
Request body
{ "key": "VIG-A8BC-D3EF-G4HJ-K5LM" }Success response
{
"valid": true,
"features": ["analytics", "export"],
"seats": { "used": 2, "max": 5 },
"devices": { "used": 1, "max": 3 },
"expiresAt": "2027-01-15T00:00:00Z"
}Invalid key response
{
"valid": false,
"reason": "REVOKED"
}Possible reason values: INVALID, EXPIRED, REVOKED, SUSPENDED, SEAT_LIMIT, DEVICE_LIMIT.
Device Activation (Public)
/api/licensing/activateRegister a device against a license key. Use this on first launch of a desktop/mobile app.
{
"key": "VIG-A8BC-D3EF-G4HJ-K5LM",
"fingerprint": "a1b2c3d4e5f6",
"name": "John's MacBook Pro"
}Returns 200 on success. Throws 409 if the device limit is reached.
/api/licensing/deactivateRemove a device from a key. Use when the user signs out or uninstalls.
{
"key": "VIG-A8BC-D3EF-G4HJ-K5LM",
"fingerprint": "a1b2c3d4e5f6"
}Offline Activation
For environments without internet access, the offline activation flow uses a signed request/response payload exchange. The admin processes the activation request on behalf of the end user.
/api/licensing/offline/activateRequires: manage:license-keysProcess an offline activation request. The requestPayload is a Base64-encoded blob generated by the SDK on the air-gapped machine.
Request body
{
"requestPayload": "eyJrZXkiOiJWSUctQThCQy1EM0VGLUc0SEotSzVMTSIsImZpbmdlcnByaW50IjoiYTFiMmMzZDRlNWY2In0="
}Success response
{
"success": true,
"data": {
"jwt": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}The jwt is a signed JWT that the end user imports into the application on the air-gapped machine.
/api/licensing/offline/deactivateDeactivate an offline device. Requires Bearer token authentication.
Request body
{
"key": "VIG-A8BC-D3EF-G4HJ-K5LM",
"fingerprint": "a1b2c3d4e5f6"
}Offline JWT Download
/api/licensing/my-licenses/[id]/offline-jwtDownload a signed JWT for a specific license. Auth: Bearer token or OAuth session cookie (portal auth). Only works for licenses with OFFLINE or HYBRID validation mode.
The response Content-Type is application/jwt with a Content-Disposition: attachment header. The JWT contains the license entitlements and can be verified offline using the tenant’s public key.
Returns 400 if the license validation mode is ONLINE (offline JWT not applicable).
Usage Tracking (Public)
/api/licensing/usageRecord a usage metric against a key. Useful for metered/consumption-based licensing.
{
"key": "VIG-A8BC-D3EF-G4HJ-K5LM",
"metric": "api_calls",
"amount": 1
}/api/licensing/usage/[key]Get current usage data for a key across all metrics.
Revocation List (Public)
/api/licensing/revocation-listReturns a signed JWT containing all revoked key JTIs. Used by the SDK for offline revocation checking.
The response is application/jwt with Cache-Control: public, max-age=3600. The SDK fetches this automatically.
Automation Rules
Automation rules connect payment events (Stripe, PayPal) to license actions (issue, activate, suspend, revoke).
/api/licensing/automationRequires: manage:license-policiesList all automation rules.
/api/licensing/automationRequires: manage:license-policiesCreate a new automation rule.
{
"name": "Stripe checkout → issue key",
"provider": "stripe",
"triggerEvent": "checkout.session.completed",
"action": "issue_key",
"policyId": "pol_abc123",
"isActive": true
}| Provider | Trigger Events |
|---|---|
stripe | checkout.session.completed, invoice.paid, payment_intent.succeeded |
paypal | PAYMENT.CAPTURE.COMPLETED |
manual | manual_trigger |
| Action | Description |
|---|---|
issue_key | Issue a new key under the linked policy |
activate_key | Activate an existing key |
suspend_key | Suspend a key |
revoke_key | Revoke a key |
/api/licensing/automation/[id]Requires: manage:license-policiesGet a single automation rule.
/api/licensing/automation/[id]Requires: manage:license-policiesUpdate an automation rule.
/api/licensing/automation/[id]Requires: manage:license-policiesDelete an automation rule.
Webhooks
These endpoints receive events from payment providers. They are verified via signature and require no Bearer token.
/api/licensing/webhooks/stripeReceives Stripe webhook events. Verified via stripe-signature header. Set STRIPE_LICENSING_WEBHOOK_SECRET in your environment.
/api/licensing/webhooks/paypalReceives PayPal webhook events. Verified via HMAC-SHA256. Set PAYPAL_LICENSING_WEBHOOK_SECRET and PAYPAL_LICENSING_WEBHOOK_ID in your environment.
Customer Portal
These endpoints are designed for end-user-facing portals where licensees can view their own licenses. Auth is via Bearer token or OAuth session cookie.
/api/licensing/my-licensesList the authenticated user’s own licenses. Returns only licenses where the user is the licensee or an assigned seat holder.
Success response
{
"success": true,
"data": [
{
"id": "key_abc123",
"keyMasked": "VIG-****-****-****-K5LM",
"policyName": "Pro Plan",
"status": "ACTIVE",
"features": ["analytics", "export"],
"seats": { "used": 2, "max": 5 },
"devices": { "used": 1, "max": 3 },
"expiresAt": "2027-01-15T00:00:00Z",
"createdAt": "2026-01-15T10:00:00Z"
}
]
}/api/licensing/my-licenses/[id]Get a single license detail for the authenticated user. Includes full seat and device lists.
Success response
{
"success": true,
"data": {
"id": "key_abc123",
"keyMasked": "VIG-****-****-****-K5LM",
"policyName": "Pro Plan",
"status": "ACTIVE",
"features": ["analytics", "export"],
"seats": {
"used": 2,
"max": 5,
"items": [
{ "userId": "user_abc", "assignedAt": "2026-02-01T10:00:00Z" }
]
},
"devices": {
"used": 1,
"max": 3,
"items": [
{ "fingerprint": "a1b2c3d4e5f6", "name": "John's MacBook Pro", "activatedAt": "2026-02-10T08:00:00Z" }
]
},
"expiresAt": "2027-01-15T00:00:00Z",
"createdAt": "2026-01-15T10:00:00Z"
}
}Licensing Audit Log
/api/licensing/auditRequires: manage:license-keysList licensing-specific audit log entries. Only returns events with the LICENSE_* action prefix.
Query parameters
| Param | Type | Description |
|---|---|---|
page | number | Page number (default: 1) |
limit | number | Items per page (default: 20) |
action | string | Filter by specific action (e.g. LICENSE_VALIDATED) |
Possible actions
| Action | Description |
|---|---|
LICENSE_CREATED | A new license key was issued |
LICENSE_VALIDATED | A key was validated |
LICENSE_ACTIVATED | A device was activated online |
LICENSE_DEACTIVATED | A device was deactivated |
LICENSE_SUSPENDED | A key was suspended |
LICENSE_REVOKED | A key was revoked |
LICENSE_REISSUED | A key was reissued with new entitlements |
LICENSE_SEAT_ASSIGNED | A seat was assigned to a user |
LICENSE_SEAT_RELEASED | A seat was released from a user |
LICENSE_CHECKOUT | A floating seat was checked out |
LICENSE_CHECKIN | A floating seat was checked in |
LICENSE_HEARTBEAT | A floating lease was extended |
LICENSE_OFFLINE_ACTIVATED | An offline activation was processed |
LICENSE_OFFLINE_DEACTIVATED | An offline device was deactivated |
LICENSE_USAGE_RECORDED | A usage metric was recorded |
Success response
{
"success": true,
"data": [
{
"id": "audit_abc123",
"action": "LICENSE_VALIDATED",
"keyId": "key_xyz789",
"keyMasked": "VIG-****-****-****-K5LM",
"actorId": null,
"ip": "203.0.113.42",
"metadata": { "valid": true },
"createdAt": "2026-03-16T12:00:00Z"
}
],
"pagination": { "page": 1, "limit": 20, "total": 350, "pages": 18 }
}Stats
/api/licensing/statsRequires: view:license-statsReturns aggregate licensing statistics.
{
"success": true,
"data": {
"totalPolicies": 3,
"totalKeys": 142,
"activeKeys": 98,
"revokedKeys": 12,
"suspendedKeys": 5,
"expiredKeys": 27,
"policyDistribution": [
{ "policyId": "pol_abc", "policyName": "Pro Plan", "count": 80 }
],
"recentKeys": []
}
}