Authentication
Every licensing endpoint except /ping and /products/{code}/public-key requires authentication. Two interchangeable schemes are supported.
Why a license key is not enough
A license key is a secret the customer holds, not a credential the server can verify. If it were the only authentication, anyone who scraped one key from a customer's .env could validate, download and probe every other license in the system.
So the two are kept strictly separate:
- the API key authenticates the calling product
- the license key in the request body authorises the license in question
Both are required. Neither substitutes for the other.
Scheme A — HMAC request signing (recommended)
Four headers accompany the request:
X-License-Key: lk_a1b2c3d4e5
X-License-Timestamp: 1791019200
X-License-Nonce: 8fj2Kd0pQmR7x1vNb3sL
X-License-Signature: <base64url HMAC>
The signature is computed over a canonical string of exactly six LF-joined lines, with no trailing newline:
<METHOD>
<path>
<canonical query string, or empty>
<sha256 hex of the raw request body>
<X-License-Timestamp>
<X-License-Nonce>
For example, signing POST /api/v1/license/activate with body {"a":1}:
POST
/api/v1/license/activate
9f4e7c... <- sha256 of {"a":1}
1791019200
8fj2Kd0pQmR7x1vNb3sL
then
signature = base64url(hmac_sha256(api_secret, canonical_string))
Canonical query string. Query parameters are sorted by key, then the pairs are sorted again as whole strings, then joined with &. This makes the signature independent of parameter order, so a proxy that reorders them cannot invalidate a legitimate request.
Replay protection
- The timestamp must be within the window (default ±300 seconds). A skewed clock produces
STALE_REQUEST; call/pingto measure the offset. - The nonce must be at least 16 characters of random data and may be used exactly once inside that window. Reuse returns
REPLAY_DETECTED.
Together these make a captured request worthless within five minutes.
Scheme B — Bearer
For curl, CI and quick integrations, a key created with signing not required accepts:
Authorization: Bearer <api_key>.<api_secret>
The secret is compared against a stored SHA-256 in constant time. Keys created with require_signature enabled — the default — reject this scheme and return INVALID_SIGNATURE, which is the safer default for production products.
Scopes
| Scope | Grants |
|---|---|
license:validate |
POST /license/validate |
license:activate |
POST /license/activate |
license:deactivate |
POST /license/deactivate |
license:status |
POST /license/status |
updates:check |
POST /updates/check |
download:request |
POST /downloads/request |
products:read |
Product metadata reads |
Grant the narrowest set that works. A key holding only license:validate cannot reach the download endpoint.
Product scoping
A key created for a product may only act on licenses belonging to that product. A key created without a product is platform-wide.
IP allowlist
An optional allowlist on the key restricts it to specific addresses. It is an absolute gate, checked before the signature, and returns IP_NOT_ALLOWED.
Rate limits
Every request is counted against several buckets simultaneously — IP, API key, license, product and installation. The tightest one wins, so rotating IP addresses or installation ids does not evade a limit.
| Bucket | Default |
|---|---|
| IP | 120 / min |
| API key | 120 / min |
| License | 60 / min |
| Product | 600 / min |
| Installation | 60 / min |
| Activation | 10 / min |
| Download | 10 / min |
Responses carry X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Dimension and, when limited, Retry-After.
TLS
The API refuses plain HTTP in production. Signing keys, license keys and installation metadata are all transmitted in these bodies; sending them in cleartext would void the whole scheme.
Page 2 of 8