Foliox License Docs

API Reference

The raw HTTP contract. Use this if you're integrating from a language without a ready-made SDK, or want to see exactly what the SDKs do under the hood.

Base URL: loading…

Conventions

Key types

Every license key has a key_type, chosen when it is created and shown in the dashboard, the CSV export, the check response and the automation API:

key_typeBehaviour
device (default)Binds to the hwid of the first device that activates it. Any other device gets hwid_mismatch until the key's device is reset. A device key can also allow several devices (max_devices: up to 3 on Starter, 5 on Pro, 15 on Business): each new device binds while there is room, and once the key is full a new device gets device_limit.
publicNever binds. Once activated, it is valid on many devices at the same time, for anyone who has it — up to the seller's plan limit (10 devices on Free, 100 on Starter, 1,000 on Pro, no limit on Business); beyond it a new device gets device_limit. Devices that already used the key keep working. There is no hwid_mismatch and nothing to reset; revoke, restore and expiry work exactly as for device keys.

The license protocol is identical for both: same requests (an hwid is still sent — it is recorded in the key's activity so you can see how many devices use it, but never enforced), same signed payload fields, same reason codes. Existing SDKs and integrations work with public keys unchanged. (A public license key has nothing to do with the Ed25519 public signing key below.)

GET /api/v1/public-key

No auth, no rate limit beyond the general limiter. Returns the Ed25519 public key used to sign every activate/verify response.

{ "publicKey": "base64-spki-der...", "algorithm": "ed25519" }

POST /api/v1/activate

Binds a key to a device on first use. Idempotent if called again with the same hwid. A public key never binds: every device's activation succeeds, and only the first one ever starts its “N days after activation” clock. Rate limited: 10 requests / 10 min per IP, and 20 / hour per key (the per-key limit doesn't apply to public keys — they are shared by design).

Request

{
  "product": "YOUR_PRODUCT_ID",
  "key": "7K2M9-QX3R8-2VBN5-8JH4C-T",
  "hwid": "sha256-hex-of-a-machine-id"    // see Security Model page
}

POST /api/v1/verify

Checks whether a previously-activated key is still valid on this device. Does not activate — a key that has never been activated returns reason: "not_activated" (SDKs handle this by calling /activate automatically). A public key verifies on any device once it has been activated at least once, by anyone. Rate limited: 30 requests / min per IP, and 120 / hour per key (the per-key limit doesn't apply to public keys).

Request

{
  "product": "YOUR_PRODUCT_ID",
  "key": "7K2M9-QX3R8-2VBN5-8JH4C-T",
  "hwid": "sha256-hex-of-a-machine-id"
}

Response (both endpoints)

A signed envelope. See the Security Model page for exactly how to verify signature against payload.

{
  "payload": {
    "valid": true,
    "reason": null,
    "product_slug": "YOUR_PRODUCT_ID",
    "key_id": 42,
    "hwid_bound": true,
    "status": "active",
    "expires_at": "2027-01-01T00:00:00.000Z",
    "issued_at": "2026-09-04T12:00:00.000Z",
    "offline_grace_until": "2026-09-07T12:00:00.000Z"
  },
  "signature": "base64..."
}

For a public key, hwid_bound is always false; the payload has exactly the same fields for both key types.

reason values

ValueMeaning
nullSuccess — valid is true.
not_foundNo such key for that product (or the key belongs to a different product).
malformed_keyThe key string fails its checksum — almost always a typo. Never hits the database.
revokedThe seller revoked this key.
expiredPast its computed or fixed expiry date.
hwid_mismatchThis key is already bound to a different device. Device keys only — a public key never returns it.
not_activated/verify only — this key has never been activated on any device yet (for a public key: by anyone yet), or — for a key allowed on several devices — not yet on this device while it still has room. Call /activate (the SDKs do).
device_limitThe key is already used on as many devices as it allows (a multi-device key, or a public key at its plan's limit). Devices already using it keep working.
suspendedThe account that issued this key is suspended. Nothing it issued validates until it is restored.
blockedThe service banned this device or IP address. Applies whatever key is presented, and is checked before anything else.

POST /api/v1/check

Customer self-service lookup: what a buyer can see about their own key, with no device ID needed. Powers the “Check a license” tool on the home page. Rate limited to 20 requests per 10 minutes per IP.

Request

{ "key": "TVMWF-WFPKW-7AWNP-C5RE5-Z" }

Response

{
  "found": true,
  "product": { "name": "Your Product", "slug": "your-product" },
  "status": "active",            // unused | active | expired | revoked
  "expires_at": "2027-01-01T00:00:00.000Z",
  "duration_days": 30,
  "days_left": 29,
  "key_type": "device",          // device | public
  "max_devices": 1,              // devices a device key may be used on
  "device_bound": true,
  "bound_at": "2026-09-04T12:00:00.000Z",
  "reset": { "available": true, "pending": false }
}

An unknown, malformed or mistyped key always returns exactly { "found": false }. The device ID, notes and customer e-mail are never included, and the response is not signed (it is informational, not an entitlement — always use /verify to decide whether software may run).

POST /api/v1/reset-request

Lets a customer ask for their key to be moved to a new computer. Resetting a device is never self-service — a leaked key would let anyone unbind it — so this only files a request that an admin approves or dismisses in the dashboard. Limits: 5 per hour per IP, 3 per day per key, one open request per key.

{ "key": "TVMWF-WFPKW-7AWNP-C5RE5-Z", "contact": "[email protected]", "message": "New laptop" }

The answer is always { "ok": true } — whether or not a request was recorded — so the endpoint can’t be used to discover which keys exist. contact and message are optional. A public key is never bound to a device, so a request for one is not recorded.

Dashboard API

The dashboards talk to private APIs under /api/account/* (your account) and /api/admin/* (the service operator). They are not public integration surfaces and are deliberately hard to script:

Your software only needs the public endpoints above. To create keys automatically — say from a checkout webhook — use the token-based automation API below (see also Automation API).

Automation API

For scripts, checkout webhooks and bots that need to create or manage keys. It uses API tokens instead of passwords: create one in the dashboard under Settings → API tokens, choose exactly the permissions it needs, and optionally pin it to your server’s IP or give it an expiry. A token is shown once, stored only as a hash, and can be revoked instantly. Base path: /api/automation/v1.

Authorization: Bearer lk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
PermissionAllows
products:readGET /products
keys:createPOST /keys — up to 100 keys per call
keys:readGET /keys/:key — status, expiry, whether it is bound, customer e-mail and notes
keys:revokePOST /keys/:key/revoke and /restore
keys:resetPOST /keys/:key/reset-device

Generate keys from a webhook

curl -X POST .../api/automation/v1/keys   -H "Authorization: Bearer $LICENSE_TOKEN"   -H "Content-Type: application/json"   -d '{"product": "your-product", "count": 1, "durationDays": 30, "customerEmail": "[email protected]"}'

# → 201 { "keys": [ { "key": "TVMWF-…", "status": "unused", "duration_days": 30, "key_type": "device", … } ] }

Add "keyType": "public" to create public keys (no device binding); leave it out, or send "device", for the usual device-locked keys. Add "maxDevices": 3 to let each key be used on up to that many devices (within your plan). Every key in a response carries its key_type and max_devices.

Errors use the same shape as the rest of the API: 401 INVALID_TOKEN (missing, wrong, expired, revoked or wrong-network token), 403 INSUFFICIENT_SCOPE, 404, 400 for invalid input, 409 PUBLIC_KEY for a device reset on a public key (it has no device), 429 when throttled. Everything a token does is recorded in the audit log under its name. Tokens can never reach admin accounts, settings, other tokens or the blocklist.

Two more errors relate to your account: 402 PLAN_LIMIT when creating something would exceed your plan (the body says which limit), and 403 ACCOUNT_SUSPENDED when the account that owns the token is suspended. See Accounts & plans.