Skip to content

API reference

The public endpoints your software calls. All are POST unless noted, accept and return JSON, and live under /v1.

Setting Value
Base URL https://api.waveformanager.com
Auth None. These are client-facing endpoints; the activation key is the credential. Never send any other secret.
Transport HTTPS only. The SDKs refuse to send a key over cleartext (loopback excepted).
Content type application/json on requests and responses. Request bodies are limited to 32 KB.
Errors Non-2xx responses carry { "error": "<code>" }. Codes are listed per endpoint and collected on Errors.
Rate limit Per client IP; see Rate limits and reliability.

Ignore response fields you do not recognize: responses can gain optional fields over time.

Two groups of endpoints: offline (signed-token issuance) and online (real-time enforcement). Which applies depends on the product’s enforcement mode.


Activate a key on a device and receive a signed license token.

Request

{
"key": "ACME-7F3K-9QPD-2MXR",
"fingerprint": "<base64url HMAC>",
"deviceName": "Studio Mac",
"productId": "<Product.id>"
}
  • key (required): The activation key.
  • fingerprint (required): This machine’s fingerprint.
  • deviceName (required): A human-readable name, 1 to 80 characters (longer is 400 bad-request); shown in device managers.
  • productId (recommended): The product’s internal Product.id, never a store SKU, woo: mapping ID, or Shopify GID. When sent, a key for a different product is refused (403 product-mismatch). Once the merchant turns on strict product binding, omitting it is refused too (403 product-id-required). Also accepted on /v1/heartbeat.
  • priorToken (optional): The token this machine held under its previous fingerprint. Send it alongside a new fingerprint when a stored token fails with fingerprint-mismatch (hardware change). On a match the old device row is replaced by the new one in the same step, so the client never sees seat-limit for its own drifted machine, and the response carries rebound: true. The new fingerprint is still counted as a new machine for the distinct-machine budget, so a re-bind can return 409 activation-limit. A priorToken that fails verification, belongs to another license, or matches no device is silently ignored and activation continues as a normal new-device request.

200 OK

{
"token": "<payload>.<signature>",
"publicKey": "<raw base64url Ed25519 public key>",
"device": { "id": "…", "name": "Studio Mac", "createdAt": 1700000000, "lastSeenAt": 1700000000 },
"reactivated": false,
"rebound": false
}
  • reactivated: true: this fingerprint was already activated, so no new seat was used.
  • rebound: true: a priorToken re-bind succeeded.

Errors

Status error Meaning
409 seat-limit Device cap reached. The body includes devices: DeviceInfo[] so you can offer a swap.
409 activation-limit The distinct-machine budget for the window is exhausted. Body: distinctMachines, windowDays. Freeing a seat will not clear this: say “contact support”, do not offer a swap.
404 invalid-key No such key.
403 revoked / expired The license was revoked, or is past its grace window.
403 blocked The license’s customer is on the merchant’s block list. Treat like revoked and drop the stored license.
403 product-mismatch / product-id-required Wrong productId for this key, or none sent where required.
403 product-not-found The productId does not exist.
403 merchant-inactive The merchant’s Waveform subscription is not active. New devices are refused; devices already registered can still re-activate.
400 bad-request Missing or invalid key, fingerprint, or deviceName.

Mint (or resume) a time-limited demo for a product, bound to one device. No key is needed.

Request

{ "productId": "<Product.id>", "fingerprint": "<base64url HMAC>", "deviceName": "Studio Mac" }

200 OK

{ "token": "<payload>.<signature>", "publicKey": "<raw base64url>", "expiresAt": 1781410876, "reissued": false }

Errors

Status error Meaning
404 product-not-found Unknown product.
403 revoked An operator revoked this machine’s demo. The client must drop its cached verdict and not retry.
403 demo-already-used This machine’s demo has simply run out.
403 blocked The fingerprint was recorded by a customer block. A demo request carries no customer identity, so only a previously seen fingerprint is caught.
403 merchant-inactive The merchant’s subscription is not active.
400 bad-request Missing productId, fingerprint, or deviceName.

Demos have a tighter per-IP rate limit than other endpoints, to bound trial abuse.


Refresh the signed token for an already-activated device. This renews the offline lease (no new seat) and is how revocation reaches machines in the field.

Request { "key": "…", "fingerprint": "…", "productId": "…" } 200 OK { "token": "…", "publicKey": "…", "device": { … } }

Status error Meaning
404 invalid-key / not-activated No such key, or this device never activated it.
403 revoked / expired / blocked A definitive verdict. Drop the stored token.
403 product-mismatch / product-id-required / product-not-found See /v1/activate.
400 bad-request Missing key or fingerprint.

List the devices bound to a key, for a “manage devices” screen. Fingerprints are never returned.

Request { "key": "…", "fingerprint": "…" }. fingerprint is optional; when sent, the row whose stored fingerprint matches is marked current: true.

200 OK

{ "devices": [ { "id": "…", "name": "Studio Mac", "createdAt": 1700000000, "lastSeenAt": 1700000000, "current": true, "worksUntil": 1702592000 } ] }

worksUntil (unix seconds) is how long that device keeps working offline: the end of the last lease signed for it plus the product’s grace. It is absent on rows that predate lease tracking. The seat-limit device list on /v1/activate carries it too. Errors: 404 invalid-key.


Free a seat, either the caller’s own (fingerprint) or a specific device from the device list (deviceId).

Request { "key": "…", "fingerprint": "…" } or { "key": "…", "deviceId": "…" } 200 OK { "removed": true }

Status error Meaning
404 invalid-key No such key.
400 bad-request Neither fingerprint nor deviceId was given.
409 support-limit { key, deviceId } only, and only on a deactivate-policy product: the license has used its remote-release budget for the rolling window. Body: used, limit, windowDays. The { key, fingerprint } self-release path is never budgeted.

See Seat policies.


Confirm a key is valid without registering a device or claiming a seat. Use it to validate a typed key before asking the customer to name their machine.

Request { "key": "…" } 200 OK { "ok": true } Errors: 404 invalid-key, 403 revoked / expired, 400 bad-request.


Public and read-only. The merchant’s Ed25519 public keys for a product, by internal Product.id. Used by the build-time key check and to confirm the key you embed.

200 OK

{ "productId": "…", "publicKeyRawB64": "<active key>", "publicKeys": ["<active>", "<keys still verifying live leases, during a rotation>"] }

404 { "error": "not-found" } for an unknown ID, including a store mapping ID. See Signing keys and rotation.


Validate a license in real time and register or refresh a concurrent session.

Request

{ "key": "ACME-7F3K-9QPD-2MXR", "instanceId": "sess_123", "label": "alex@example.com · Chrome" }
  • instanceId (optional but recommended): a stable ID for this session or seat, up to 200 characters. Re-sending the same one refreshes the session instead of consuming a slot. Omit it to validate without taking a slot, but the cap still applies: when every slot is taken, a call without an instanceId is refused with 409 session-limit too. A longer value is treated as omitted, so keep it short.
  • label (optional): up to 120 characters, shown in dashboards. A longer value is ignored.

200 OK

{
"ok": true, "status": "active", "mode": "online",
"features": ["pro"], "expiresAt": 1781410876,
"sessions": { "used": 2, "max": 3 },
"usage": { "used": 423, "limit": 1000, "period": "2026-05" }
}

usage.limit is null when the product has no usage cap. period is "YYYY-MM", "YYYY-MM-DD", or "total", depending on the product configuration.

Status error Meaning
409 session-limit Too many concurrent sessions. Body: sessions: SessionInfo[], max.
404 invalid-key No such key.
403 revoked / expired The license was revoked or has expired.
400 bad-request Missing key.

Record metered usage for the current period and enforce the limit.

Request { "key": "…", "amount": 1 }. amount defaults to 1, is rounded down to an integer, and is capped at 10,000 per call. 200 OK { "allowed": true, "usage": { "used": 424, "limit": 1000, "period": "2026-05" } } 429 { "allowed": false, "usage": { "used": 1000, "limit": 1000, "period": "2026-05" } }: the limit would be exceeded, and the counter is not incremented. Errors: 404 invalid-key, 403 revoked / expired.


End a session (call it on logout) to free a concurrency slot immediately. Sessions also expire automatically after the product’s session TTL (default 15 minutes).

Request { "key": "…", "instanceId": "sess_123" } 200 OK { "ended": true } Errors: 404 invalid-key, 400 bad-request.


type DeviceInfo = { id: string; name: string; createdAt: number; lastSeenAt: number; current?: boolean; worksUntil?: number };
type SessionInfo = { id: string; instanceId: string; label?: string; createdAt: number; lastSeenAt: number };
// all timestamps are unix seconds

The signed token payload is specified on Token format.