Every non-2xx response carries { "error": "<code>" }. The tables give the HTTP status, the meaning, and the recommended
reaction. Definitive verdicts are server decisions about the license itself; act on them. Transient ones are about the
network or load; keep the customer working and retry.
error |
Status |
Meaning |
What to do |
invalid-key |
404 |
No such key. |
Ask the customer to re-enter it. |
revoked |
403 |
The license was revoked (refund, chargeback, support action). On /v1/demo: an operator revoked this machine’s demo. |
Drop the stored token and key now. Do not retry. |
expired |
403 |
The license is past its end date and grace window. |
Drop the token; point the customer to renew. |
blocked |
403 |
The license’s customer is on the merchant’s block list. |
Treat exactly like revoked. |
not-activated |
404 |
/v1/heartbeat for a device that never activated this key. |
Clear the stored token and ask the customer to activate. |
demo-already-used |
403 |
This machine’s demo has run out. |
Show the purchase prompt. |
product-mismatch |
403 |
The key is for a different product than the productId sent. |
A configuration bug on your side, or the wrong key; check the product ID. |
product-id-required |
403 |
The product has strict binding on and the request omitted productId. |
Send productId on activate and heartbeat. |
product-not-found |
403 / 404 |
The productId is unknown (404 on /v1/demo, 403 elsewhere). |
Check the product ID against the Integration page. |
merchant-inactive |
403 |
The merchant’s Waveform subscription is not active. New devices are refused; already-registered devices can still re-activate. |
Tell the customer to contact the software vendor. |
error |
Status |
Meaning |
What to do |
seat-limit |
409 |
Device cap reached. The body lists devices. |
Offer to deactivate one, then retry. |
activation-limit |
409 |
The distinct-machine budget for the rolling window is used up. Body: distinctMachines, windowDays. |
Do not offer a swap; it cannot succeed. Say “contact support”. |
support-limit |
409 |
A remote device release exceeded the budget (deactivate policy). Body: used, limit, windowDays. |
Show the numbers and tell the customer to contact support. |
session-limit |
409 |
Online products: too many concurrent sessions. Body: sessions, max. |
Offer to end another session, or deny. |
rate-limited |
429 |
Too many requests from this IP. Carries a Retry-After header. |
Back off for Retry-After seconds. Cache validate results. |
(usage) allowed: false |
429 |
/v1/usage: the period’s metered limit would be exceeded. |
Show an upgrade prompt. The counter was not incremented. |
error |
Status |
Meaning |
bad-request |
400 |
A required field is missing, empty, or too long. |
A connection failure, a timeout, or a 5xx is not a verdict. The SDK reports these as error: "network". Keep the customer
working, keep the stored token, and retry later. The hosted API can cold-start after idle, so use generous timeouts and retry
(see Rate limits and reliability).
These come from verifying a token locally, not from the API. See Token format for what triggers each.
reason |
Meaning |
malformed-token, malformed-payload |
The token is not in the expected format. |
bad-signature |
The signature does not verify: forged, tampered, or signed by a different key. |
unknown-key |
The token names a signing key your build does not hold. Ship a build that accepts the new key, or re-activate. |
alg-mismatch, unsupported-version |
Not Ed25519 / schema version 1. Downgrade attempt. |
invalid-claims |
The signature is good but a claim has the wrong type or an out-of-range value. |
product-mismatch |
A token for a different product. |
fingerprint-mismatch, fingerprint-required |
The token is bound to another machine, or no fingerprint was supplied (fail closed). |
not-yet-valid |
iat is more than five minutes in the future. |
expired |
Past the lease, license end, and grace. |
online-check-required |
The SDK’s offline gate: the app ran longer than maxOfflineDays without a check-in. |