Skip to Content

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:

AudienceAuthEndpoints
Your app / SDK (runtime)No Bearer token needed/validate, /activate, /deactivate, /usage, /revocation-list
Admin / Console (management)Bearer token + permissionEverything else

All endpoints require the x-tenant header.


Policies

Policies define what a license grants — features, seats, devices, expiry, key format, and validation mode.

GET/api/licensing/policiesRequires: manage:license-policies

List all policies for the current tenant. Supports pagination and search.

Query parameters

ParamTypeDescription
pagenumberPage number (default: 1)
limitnumberItems per page (default: 20)
searchstringFilter 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 } }
POST/api/licensing/policiesRequires: manage:license-policies

Create 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" } } }
FieldTypeRequiredDescription
namestringYesDisplay name
slugstringYesUnique slug (e.g. pro-plan)
validationModeONLINE | HYBRID | OFFLINENoDefault: HYBRID
dimensionsobjectNoSeats, devices, features, expiry, key format config
offlineGraceDaysnumberNoDays a key stays valid offline (default: 7)
revocationTtlMinnumberNoMinutes before revocation propagates (default: 60)
GET/api/licensing/policies/[id]Requires: manage:license-policies

Get a single policy by ID.

PATCH/api/licensing/policies/[id]Requires: manage:license-policies

Update a policy. Only provided fields are changed.

DELETE/api/licensing/policies/[id]Requires: manage:license-policies

Delete 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).

GET/api/licensing/keysRequires: manage:license-keys

List all keys. Filterable by status and policy.

Query parameters

ParamTypeDescription
pagenumberPage number
limitnumberItems per page
policyIdstringFilter by policy
statusACTIVE | SUSPENDED | REVOKED | EXPIREDFilter by status
POST/api/licensing/keysRequires: manage:license-keys

Issue 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).

GET/api/licensing/keys/[id]Requires: manage:license-keys

Get a single key by ID, including related policy, seats, and devices.

POST/api/licensing/keys/[id]/suspendRequires: manage:license-keys

Suspend a key. It can be reactivated later.

POST/api/licensing/keys/[id]/revokeRequires: manage:license-keys

Permanently revoke a key.

POST/api/licensing/keys/[id]/reissueRequires: manage:license-keys

Reissue 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

GET/api/licensing/keys/[id]/seatsRequires: manage:license-keys

List all seats (assigned users) for a key.

POST/api/licensing/keys/[id]/seatsRequires: manage:license-keys

Assign a seat to a user. Fails if the seat limit is reached.

{ "userId": "user_abc" }
DELETE/api/licensing/keys/[id]/seats/[userId]Requires: manage:license-keys

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

POST/api/licensing/checkout

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

POST/api/licensing/checkin

Check 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" }
POST/api/licensing/heartbeat

Extend 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

GET/api/licensing/floatingRequires: manage:license-keys

List 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" } ] }
DELETE/api/licensing/floating?seatId=xxxRequires: manage:license-keys

Force-release a floating seat. Use this to free up stuck seats without waiting for the lease to expire.

Query parameters

ParamTypeDescription
seatIdstringThe ID of the floating seat to release

Devices

GET/api/licensing/keys/[id]/devicesRequires: manage:license-keys

List all activated devices for a key.

DELETE/api/licensing/keys/[id]/devices/[deviceId]Requires: manage:license-keys

Remove a device from a key.


Device Registry

GET/api/licensing/devicesRequires: manage:license-keys

List all devices across all license keys for the tenant. Supports pagination, search, and filtering by activation method.

Query parameters

ParamTypeDescription
pagenumberPage number (default: 1)
limitnumberItems per page (default: 20)
searchstringFilter by fingerprint or device name
methodONLINE | OFFLINEFilter 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.

POST/api/licensing/validate

Validate 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)

POST/api/licensing/activate

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

POST/api/licensing/deactivate

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

POST/api/licensing/offline/activateRequires: manage:license-keys

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

POST/api/licensing/offline/deactivate

Deactivate an offline device. Requires Bearer token authentication.

Request body

{ "key": "VIG-A8BC-D3EF-G4HJ-K5LM", "fingerprint": "a1b2c3d4e5f6" }

Offline JWT Download

GET/api/licensing/my-licenses/[id]/offline-jwt

Download 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)

POST/api/licensing/usage

Record a usage metric against a key. Useful for metered/consumption-based licensing.

{ "key": "VIG-A8BC-D3EF-G4HJ-K5LM", "metric": "api_calls", "amount": 1 }
GET/api/licensing/usage/[key]

Get current usage data for a key across all metrics.


Revocation List (Public)

GET/api/licensing/revocation-list

Returns 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).

GET/api/licensing/automationRequires: manage:license-policies

List all automation rules.

POST/api/licensing/automationRequires: manage:license-policies

Create a new automation rule.

{ "name": "Stripe checkout → issue key", "provider": "stripe", "triggerEvent": "checkout.session.completed", "action": "issue_key", "policyId": "pol_abc123", "isActive": true }
ProviderTrigger Events
stripecheckout.session.completed, invoice.paid, payment_intent.succeeded
paypalPAYMENT.CAPTURE.COMPLETED
manualmanual_trigger
ActionDescription
issue_keyIssue a new key under the linked policy
activate_keyActivate an existing key
suspend_keySuspend a key
revoke_keyRevoke a key
GET/api/licensing/automation/[id]Requires: manage:license-policies

Get a single automation rule.

PATCH/api/licensing/automation/[id]Requires: manage:license-policies

Update an automation rule.

DELETE/api/licensing/automation/[id]Requires: manage:license-policies

Delete an automation rule.


Webhooks

These endpoints receive events from payment providers. They are verified via signature and require no Bearer token.

POST/api/licensing/webhooks/stripe

Receives Stripe webhook events. Verified via stripe-signature header. Set STRIPE_LICENSING_WEBHOOK_SECRET in your environment.

POST/api/licensing/webhooks/paypal

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

GET/api/licensing/my-licenses

List 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" } ] }
GET/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

GET/api/licensing/auditRequires: manage:license-keys

List licensing-specific audit log entries. Only returns events with the LICENSE_* action prefix.

Query parameters

ParamTypeDescription
pagenumberPage number (default: 1)
limitnumberItems per page (default: 20)
actionstringFilter by specific action (e.g. LICENSE_VALIDATED)

Possible actions

ActionDescription
LICENSE_CREATEDA new license key was issued
LICENSE_VALIDATEDA key was validated
LICENSE_ACTIVATEDA device was activated online
LICENSE_DEACTIVATEDA device was deactivated
LICENSE_SUSPENDEDA key was suspended
LICENSE_REVOKEDA key was revoked
LICENSE_REISSUEDA key was reissued with new entitlements
LICENSE_SEAT_ASSIGNEDA seat was assigned to a user
LICENSE_SEAT_RELEASEDA seat was released from a user
LICENSE_CHECKOUTA floating seat was checked out
LICENSE_CHECKINA floating seat was checked in
LICENSE_HEARTBEATA floating lease was extended
LICENSE_OFFLINE_ACTIVATEDAn offline activation was processed
LICENSE_OFFLINE_DEACTIVATEDAn offline device was deactivated
LICENSE_USAGE_RECORDEDA 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

GET/api/licensing/statsRequires: view:license-stats

Returns 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": [] } }