Validation flow
1. Pick a fingerprint
A fingerprint is any stable string that identifies a machine or install: a hashed machine id, a hardware UUID, or a random id you persist on first run. Hash it (SHA-256) before sending. Do not use user names or emails.
2. Activate once
POST /api/v1/licenses/activate
{ "key": "ACME-…", "fingerprint": "sha256:…", "name": "Jane's MacBook", "platform": "darwin", "app_version": "2.4.1" }
201 → { "ok": true, "data": { "valid": true, "code": "VALID", "activation": { "id": "act_…" }, "seats": { "max": 3, "used": 1 } } }
422 → { "ok": false, "error": { "code": "NO_SEATS", ... }, "details": { "result": { ... } } }Activation is idempotent for the same fingerprint, so it is safe to call on every launch if you prefer a single call.
3. Validate on launch (and periodically)
POST /api/v1/licenses/validate
{ "key": "ACME-…", "fingerprint": "sha256:…", "nonce": "a4f1…" }
200 → { "ok": true, "data": { "valid": true, "code": "VALID", "expires_at": "2027-01-01T00:00:00.000Z",
"entitlements": { "pro": true, "max_projects": 10 }, "license": { ... } } }Cache the last successful result locally and allow a short offline window; use certificates for longer offline periods.
4. Floating seats: heartbeat
With a floating policy each activation holds a lease. Send POST /api/v1/licenses/heartbeat at half the lease interval; when a machine stops, its seat is freed automatically. Node-locked policies can also release idle seats via the policy's heartbeat cull setting.
5. Deactivate when the user signs out
POST /api/v1/licenses/deactivate { "key": "ACME-…", "fingerprint": "sha256:…" }Verifying response signatures
Every response carries SoftLicense-Signature: kid=…,ts=…,nonce=…,sig=…. The signature is Ed25519 over the canonical string ts + "\n" + nonce + "\n" + sha256hex(body). Send a random nonce with each request and require that it is echoed; reject responses older than a few minutes. Fetch the public key once from GET /api/v1/public-key and embed it in your app. This defeats proxies that rewrite valid to true or replay old responses.
Rate limits
300 requests per minute per IP and 600 per minute per key. A 429 includes a Retry-After header. Validate on launch and then at most hourly.