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

FieldTypeRequiredDescription
licenseKeystringYesThe plaintext license key (PERMIT-XXXX-XXXX-XXXX-XXXX format)
noncestringYesSingle-use token from GET /api/v1/nonce — prevents replay attacks
deviceIdstringNoSHA-256 hardware fingerprint — required for node-locked licenses
deviceNamestringNoHuman-readable device label (e.g. "John-MacBook-Pro"), shown in admin panel
versionstringNoYour app version (e.g. "2.3.1") — validated against MinVersion/MaxVersion on the license
userEmailstringNoEnd-user's email address — stored for B2B named-user audit trail, visible in admin panel
expectedProductIdstring (GUID)NoReject (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.