Authentication

v1 JSON over HTTPS Bearer credentials or HMAC signatures

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 /ping to 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