License keys
Your buyer gets a key. Your software asks us whether it is good.
The three calls
All three take the same body and return the same shape. They are
POST,
JSON in and JSON out, and need no authentication — the key is the credential.
| /api/licenses/v1/activate | Claims a machine. Call it once, when the buyer enters the key. |
| /api/licenses/v1/validate | Checks it still stands. Call it on launch. Never claims a machine. |
| /api/licenses/v1/deactivate | Gives the machine back, so the buyer can move to another. |
Request
{
"key": "H4KM-9QP2-7T1B-XR3D",
"instanceId": "b8f3…",
"instanceName": "Nate's MacBook"
}
- key
- Whatever the buyer pasted. Case and dashes are ignored, so you do not need to clean it up first.
- instanceId
- Your identifier for this installation. It has to be the same every launch, or each start takes another machine.
- instanceName
- Optional. Shown to the buyer in the list of their own machines, so make it something they would recognise.
Response
{
"valid": true,
"reason": "ok",
"message": "This key is active on this machine.",
"productId": "…",
"productName": "Acme Synth",
"activationsUsed": 2,
"activationLimit": 3,
"expiresAt": null
}
Always HTTP 200 unless the request itself was malformed. A key that is not
valid is an answer, not an error — branch on
valid,
not on the status code. The one status worth handling separately is
429,
which means you are calling far more often than any real application needs to.
reason is
a stable string, meant for branching:
| ok | Good. Carry on. |
| not_found | No such key. Also what a nonsense key returns. |
| revoked | Refunded, disputed, or withdrawn by you. |
| expired | The subscription behind it ended. |
| limit_reached | Every machine is taken. Tell them to free one. |
| not_activated | Valid key, but not on this machine yet. Call activate. |
Two things worth knowing
The machine limit is only as strong as your instance id. We cannot fingerprint a computer from an HTTP request — we count the distinct ids you send us. If you send a fresh random value on every launch, the limit does nothing, and if you send the same constant for every install, one key covers the world. Derive it from something stable about the machine and store it beside your settings.
Decide what happens when we are unreachable. Networks fail and laptops go on planes. Refusing to start because a check timed out punishes the paying customer for our downtime. Cache the last good answer with a timestamp and keep running for a grace period you are comfortable with — a week is common — then ask again.
Versioning
The v1 in
the path is a promise. Fields may be added to the response; nothing will be
removed or change meaning. If we ever need a shape that breaks that,
it becomes v2
and v1 keeps answering — your buyer's copy of version 1.4 will still work
long after you have stopped shipping it.