POST /api/v1/license/activate
Register a machine against a license key on first run. Idempotent for known machines.
POST /api/v1/license/activate
Call this once when the plugin starts up on a new machine. The server records the (licenseKey, machineId) pair and returns the license metadata the plugin caches locally.
The call is idempotent for a known machine: a repeat request with the same machineId returns 200 with the existing activation refreshed (lastSeenAt updated, machineName optionally replaced). A repeat with a new machineId either creates a new activation or returns 409 if the cap is reached.
Request
Headers: see HMAC Signing.
Body (JSON):
{
"licenseKey": "uuid-v4", // required, the customer's license key
"machineId": "8-128 chars", // required, your stable per-machine identifier
"machineName": "optional label" // optional, max 100 chars, shown on dashboard
}machineId guidance
Pick something stable across reboots and Unity Editor restarts. Suggested derivation: SHA-256 of the OS machine GUID + the Unity Editor install path. Plugins must not use values that change at install time (e.g. Process.GetCurrentProcess().Id).
Responses
200 OK — created or refreshed
{
"ok": true,
"product": { "slug": "procgenerator", "name": "Procedural Generator" },
"status": "ACTIVE",
"expiresAt": null,
"activations": 1,
"max": 3
}activations is the current count of distinct machines including this one. max is the per-license cap.
400 LICENSE_API_MALFORMED (1702)
Body did not match the schema (missing field, wrong type, machineId too short, etc.).
401 LICENSE_API_BAD_SIGNATURE (1700) / STALE_TIMESTAMP (1701)
Signature, headers, or timestamp failed verification. See HMAC Signing for the order in which checks fire.
403 LICENSE_API_INACTIVE (1704)
The license exists but is not in the ACTIVE state. Response includes the current status field so the plugin can surface a precise reason (refunded, suspended, etc.).
404 LICENSE_API_NOT_FOUND (1703)
The license key is unknown or has been soft-deleted.
409 LICENSE_API_LIMIT_REACHED (1705)
Adding this machine would exceed the activation cap. Body includes the current count and the cap:
{
"error": "Maximum 3 activations reached",
"code": 1705,
"activations": 3,
"max": 3
}The user can free a slot from https://ntkfoundry.com/dashboard/activations. Plugins should surface a hint pointing to that URL.
429 LICENSE_API_RATE_LIMITED (1706)
Per-license-key or per-IP bucket tripped. The response includes the bucket that fired ("key" or "ip") and a Retry-After: <seconds> header. Honour the header.
500 internal
Unexpected server-side failure. Logged + reported to Sentry. Plugins should retry with exponential backoff capped at the activation timeout.
Concurrency
The activation flow runs in a transaction with a SELECT … FOR UPDATE row lock on the License row. Two concurrent activations for the same license queue at the lock — neither can race past the cap.
Audit log
On accept the server writes an audit record (action: "license.activate" for new machines, action: "license.heartbeat" with refresh: true for repeats). These are visible to the license owner and to the support team.