Skip to content

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"]
curl -s -X POST \
  https://us-central1-cg-license-server.cloudfunctions.net/activate \
  -H "Content-Type: application/json" \
  -d '{
    "licenseKey": "XXXX-XXXX-XXXX-XXXX",
    "machineFingerprint": "sha256-of-machine-id",
    "hostname": "my-workstation",
    "productId": "prod_abc123"
  }'

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

import requests

resp = requests.post(
    "https://us-central1-cg-license-server.cloudfunctions.net/validate",
    json={
        "licenseKey": "XXXX-XXXX-XXXX-XXXX",
        "machineFingerprint": "sha256-of-machine-id",
        "token": "eyJ...",
    },
)
data = resp.json()
if data["valid"]:
    new_token = data["newToken"]
curl -s -X POST \
  https://us-central1-cg-license-server.cloudfunctions.net/validate \
  -H "Content-Type: application/json" \
  -d '{
    "licenseKey": "XXXX-XXXX-XXXX-XXXX",
    "machineFingerprint": "sha256-of-machine-id",
    "token": "eyJ..."
  }'

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

{
  "licenseKey": "XXXX-XXXX-XXXX-XXXX",
  "productId": "prod_abc123"
}
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/verify endpoint.
  • Bundle support: a bundle license can unlock multiple productId values depending on its unlockedProducts list.

Examples

import requests

resp = requests.post(
    "https://us-central1-cg-license-server.cloudfunctions.net/verify",
    json={
        "licenseKey": "XXXX-XXXX-XXXX-XXXX",
        "productId": "prod_abc123",
    },
)
data = resp.json()
if data["valid"]:
    print(f"Variant: {data['variant']}")
curl -s -X POST \
  https://us-central1-cg-license-server.cloudfunctions.net/verify \
  -H "Content-Type: application/json" \
  -d '{
    "licenseKey": "XXXX-XXXX-XXXX-XXXX",
    "productId": "prod_abc123"
  }'

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

{
  "success": true,
  "machinesUsed": 0
}

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

import requests

resp = requests.post(
    "https://us-central1-cg-license-server.cloudfunctions.net/deactivate",
    json={
        "licenseKey": "XXXX-XXXX-XXXX-XXXX",
        "machineFingerprint": "sha256-of-machine-id",
        "token": "eyJ...",
    },
)
data = resp.json()
print(f"Machines now used: {data['machinesUsed']}")
curl -s -X POST \
  https://us-central1-cg-license-server.cloudfunctions.net/deactivate \
  -H "Content-Type: application/json" \
  -d '{
    "licenseKey": "XXXX-XXXX-XXXX-XXXX",
    "machineFingerprint": "sha256-of-machine-id",
    "token": "eyJ..."
  }'

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

{
  "success": true,
  "sessionActive": true
}

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()
curl -s -X POST \
  https://us-central1-cg-license-server.cloudfunctions.net/heartbeat \
  -H "Content-Type: application/json" \
  -d '{
    "licenseKey": "XXXX-XXXX-XXXX-XXXX",
    "machineFingerprint": "sha256-of-machine-id",
    "token": "eyJ..."
  }'