Client Endpoints¶
No authentication required. All client endpoints use POST with a JSON body.
Activate License¶
Binds a license key to a machine and returns a session token.
POST /activate
Request¶
{
"licenseKey": "XXXX-XXXX-XXXX-XXXX",
"machineFingerprint": "sha256-of-machine-id",
"hostname": "my-workstation",
"productId": "prod_abc123"
}
| Parameter | Type | Required | Description |
|---|---|---|---|
licenseKey |
string | Yes | The license key to activate |
machineFingerprint |
string | Yes | Stable unique identifier for this machine |
hostname |
string | Yes | Human-readable machine name for the dashboard |
productId |
string | Yes | Product the license is being activated against |
Success Response 200¶
{
"success": true,
"token": "eyJ...",
"expiresAt": "2025-01-01T00:00:00Z",
"machinesUsed": 1,
"maxMachines": 3,
"status": "active",
"variant": "pro",
"message": "License is active",
"licenseExpiresAt": "2026-01-01T00:00:00Z"
}
Errors¶
| Status | Error | Cause |
|---|---|---|
400 |
Missing required fields |
licenseKey, machineFingerprint, hostname, or productId not provided |
403 |
License has been revoked |
License has been revoked |
403 |
License has expired |
License passed its expiresAt date |
403 |
License is for a different product |
License is not valid for the given productId |
403 |
Machine limit reached (N). Deactivate another machine first. |
machinesUsed equals maxMachines |
404 |
License not found |
No license exists for the given key |
429 |
Too many activation attempts. Try again later. |
Rate limit exceeded (15 attempts per hour per key) |
Examples¶
import requests
resp = requests.post(
"https://us-central1-cg-license-server.cloudfunctions.net/activate",
json={
"licenseKey": "XXXX-XXXX-XXXX-XXXX",
"machineFingerprint": "sha256-of-machine-id",
"hostname": "my-workstation",
"productId": "prod_abc123",
},
)
data = resp.json()
token = data["token"]
Validate License¶
Validates an existing session token and issues a refreshed token. Call this on each application launch or session check.
POST /validate
Request¶
{
"licenseKey": "XXXX-XXXX-XXXX-XXXX",
"machineFingerprint": "sha256-of-machine-id",
"token": "eyJ..."
}
| Parameter | Type | Required | Description |
|---|---|---|---|
licenseKey |
string | Yes | The license key |
machineFingerprint |
string | Yes | Must match the fingerprint used during activation |
token |
string | Yes | Session token from the last activate or validate call |
Success Response 200¶
{
"valid": true,
"newToken": "eyJ...",
"expiresAt": "2025-01-02T00:00:00Z",
"status": "active",
"email": "user@example.com",
"productId": "prod_abc123",
"variant": "pro",
"licenseExpiresAt": "2026-01-01T00:00:00Z",
"daysRemaining": 358
}
newToken is conditional
newToken is only returned when the token is within 7 days of expiry or the license status is not active. If no refresh is needed, newToken is absent from the response.
Errors¶
| Status | Error | Cause |
|---|---|---|
400 |
Missing required fields |
Required parameters not provided |
401 |
Invalid or expired token |
Token is malformed or expired |
401 |
Token mismatch |
Token does not match the provided licenseKey or machineFingerprint |
403 |
License has expired |
License passed its expiresAt date |
403 |
License has been revoked |
License has been revoked |
403 |
Machine not activated |
This fingerprint has no active activation for the key |
404 |
License not found |
No license exists for the given key |
Examples¶
Verify License¶
Checks whether a license key is valid for a product without binding to a machine. Compatible with the Gumroad license format. Supports bundle licenses that unlock multiple products.
POST /verify
Request¶
| Parameter | Type | Required | Description |
|---|---|---|---|
licenseKey |
string | Yes | The license key to verify |
productId |
string | Yes | Product to verify the license against |
Success Response 200¶
{
"valid": true,
"email": "user@example.com",
"productId": "prod_abc123",
"variant": "pro",
"licenseType": "per-machine",
"status": "active",
"machinesUsed": 1,
"maxMachines": 3,
"createdAt": "2024-01-01T00:00:00Z",
"purchasedAt": "2024-01-01T00:00:00Z",
"licenseExpiresAt": null,
"daysRemaining": null
}
Errors¶
| Status | Error | Cause |
|---|---|---|
400 |
Missing licenseKey or productId |
Required parameters not provided |
403 |
License is for a different product |
License is not valid for the given productId |
403 |
License has expired |
License passed its expiresAt date |
403 |
License has been revoked |
License has been revoked |
404 |
License not found |
No license exists for the given key |
Notes¶
- No machine binding occurs. Safe to call frequently.
- Gumroad-compatible: use this as a drop-in replacement for Gumroad's
/licenses/verifyendpoint. - Bundle support: a bundle license can unlock multiple
productIdvalues depending on itsunlockedProductslist.
Examples¶
Deactivate License¶
Removes a machine activation from a license. The session token proves ownership of the machine slot being released.
POST /deactivate
Request¶
{
"licenseKey": "XXXX-XXXX-XXXX-XXXX",
"machineFingerprint": "sha256-of-machine-id",
"token": "eyJ..."
}
| Parameter | Type | Required | Description |
|---|---|---|---|
licenseKey |
string | Yes | The license key |
machineFingerprint |
string | Yes | Fingerprint of the machine to deactivate |
token |
string | Yes | Valid session token for this machine (proves ownership) |
Success Response 200¶
Errors¶
| Status | Error | Cause |
|---|---|---|
400 |
Missing required fields (licenseKey, machineFingerprint, token) |
Required parameters not provided |
401 |
Invalid or expired token |
Token is invalid or expired |
401 |
Token mismatch |
Token does not match the provided licenseKey or machineFingerprint |
404 |
License not found |
No license exists for the given key |
404 |
Machine not activated |
The given fingerprint has no active activation for this key |
Notes¶
- The token requirement prevents one machine from deactivating another machine's slot.
- After deactivation, the freed slot is immediately available for a new activation.
Examples¶
Heartbeat¶
Keeps a floating license session alive. Only applies to licenses with licenseType: "floating".
POST /heartbeat
Request¶
{
"licenseKey": "XXXX-XXXX-XXXX-XXXX",
"machineFingerprint": "sha256-of-machine-id",
"token": "eyJ..."
}
| Parameter | Type | Required | Description |
|---|---|---|---|
licenseKey |
string | Yes | The license key |
machineFingerprint |
string | Yes | Fingerprint of the active machine |
token |
string | Yes | Current session token |
Success Response 200¶
Errors¶
| Status | Error | Cause |
|---|---|---|
400 |
Missing required fields |
licenseKey, machineFingerprint, or token not provided |
401 |
Invalid token |
Token is invalid or does not match the license key |
404 |
License not found |
No license exists for the given key |
404 |
Machine not activated |
The given fingerprint has no active activation for this key |
Notes¶
- Floating licenses only. For per-machine and site licenses, heartbeat returns success but has no effect.
- Call every 5 to 15 minutes while the application is open.
- If no heartbeat is received within approximately 15 minutes, the session expires and the concurrent slot is released.
- This allows another machine to take the slot without an explicit deactivate call.
Examples¶
import requests
import threading
def heartbeat_loop(license_key, fingerprint, token, interval=300):
"""Call every `interval` seconds (default 5 min)."""
resp = requests.post(
"https://us-central1-cg-license-server.cloudfunctions.net/heartbeat",
json={
"licenseKey": license_key,
"machineFingerprint": fingerprint,
"token": token,
},
)
return resp.json()