POST /activate
POST /api/v1/activate
Validates the key and registers an installation. A new count-based activation consumes a seat; node-locked reactivation with the same device ID reuses its existing seat. Use this only when a customer "registers" your software on a specific machine for the first time. Once the key's max activations are reached, this endpoint returns an error.
Nonce required: You must call GET /api/v1/nonce first
and include the returned value as the
nonce field. Nonces are single-use and expire in 5 minutes.
Request body fields
| Field | Type | Required | Description |
|---|---|---|---|
licenseKey | string | Yes | The plaintext license key (PERMIT-XXXX-XXXX-XXXX-XXXX format) |
nonce | string | Yes | Single-use token from GET /api/v1/nonce — prevents replay attacks |
deviceId | string | No | SHA-256 hardware fingerprint — required for node-locked licenses |
deviceName | string | No | Human-readable device label (e.g. "John-MacBook-Pro"), shown in admin panel |
version | string | No | Your app version (e.g. "2.3.1") — validated against MinVersion/MaxVersion on the license |
userEmail | string | No | End-user's email address — stored for B2B named-user audit trail, visible in admin panel |
expectedProductId | string (GUID) | No | Reject (errorCode: "WrongProduct") unless this key belongs to this exact product. Without it, any active key for the caller's tenant activates successfully regardless of which product it was issued for. Find your product's ID in the Admin panel under Products. |
JSON Body — full example
{
"licenseKey": "PERMIT-A3K7-MN2P-QR9X-TZ5W",
"nonce": "a3f82b1c9e4d7f06bc2e5a1d3c8f7e92",
"deviceId": "a3f8d2b1c9e4...",
"deviceName": "John-MacBook-Pro-M3",
"version": "2.3.1",
"userEmail": "[email protected]",
"expectedProductId": "7f3e9a2b-1c4d-4e8f-9a6b-3d2c1e0f5a8b"
}cURL — two-step flow (fetch nonce then activate)
# Step 1: get a nonce NONCE=$(curl -s https://api.permitcore.dev/api/v1/nonce | python3 -c "import sys,json; print(json.load(sys.stdin)['nonce'])") # Step 2: activate (expectedProductId is optional — see the field table above) curl -X POST https://api.permitcore.dev/api/v1/activate \ -H "Content-Type: application/json" \ -d "{\"licenseKey\": \"PERMIT-A3K7-MN2P-QR9X-TZ5W\", \"nonce\": \"$NONCE\", \"expectedProductId\": \"7f3e9a2b-1c4d-4e8f-9a6b-3d2c1e0f5a8b\"}"
Success response (200 OK)
JSON
{
"isValid": true,
"productName": "Acme Video Editor Pro",
"productId": "7f3e9a2b-1c4d-4e8f-9a6b-3d2c1e0f5a8b",
"remainingActivations": 2,
"expiresAt": null,
"features": ["export", "api"],
"isTrial": false,
"nodeLocked": true,
"offlineGraceDays": 7
}Max activations reached (200, isValid: false)
JSON
{
"isValid": false,
"message": "Maximum activations reached for this license",
"errorCode": "SeatsExhausted"
}Wrong product (200, isValid: false) — only when expectedProductId is sent
JSON
{
"isValid": false,
"message": "This license does not cover the requested product.",
"errorCode": "WrongProduct",
"productId": "a1b2c3d4-5678-90ab-cdef-1234567890ab"
}Activation is atomic: The counter is incremented using a database-level atomic operation.
You don't need to worry about race conditions even if multiple machines try to activate the same key simultaneously.