Public license routes (add-on)
Unauthenticated routes that answer software running on a customer machine, with the key as the credential.
These answer software running on a customer's machine. No account, no permission, no API key: the licence key is the credential. What each one returns is on Licenses.
Every one of the seven paths also answers a CORS preflight, so a browser client gets one even if the API plugin's CORS middleware is ever moved. The seven are OPTIONS /kahunacart/licenses/public/activate, OPTIONS /kahunacart/licenses/public/deactivate, OPTIONS /kahunacart/licenses/public/file, OPTIONS /kahunacart/licenses/public/latest, OPTIONS /kahunacart/licenses/public/release, OPTIONS /kahunacart/licenses/public/updates and OPTIONS /kahunacart/licenses/public/validate. They answer the browser rather than a caller, so none of them has an MCP tool or an endpoint entry of its own, and a client written against the routes below never calls one by hand.
Validate a license key
/api/v1/kahunacart/licenses/public/validate
Ask whether a license key is good, the way a customer's own software does, and get back the status, the product, the seat counts, the two dates, the key's metadata and covers, every product and package slug the key is good for. This route is public and unauthenticated, because it answers software running on a customer's machine and the key itself is the credential. Suspended, revoked and expired are answers rather than errors: the body carries valid: false and names the status, and only active sets valid: true. Two things do refuse: a key nobody bought is 404 license_not_found, and a key that does not cover the product product names is 409 product_mismatch. Keys are matched on their letters and digits alone, so any spacing, case or separators work. Nothing about the entitlement changes, though the key's last-checked time is stamped.
Parameters
| Name | Type | Description |
|---|---|---|
| key required | string | The license key, in any spacing, case or separators |
| instance optional | string | Names one installation, for example a machine id or a site URL. When sent, the answer says whether this installation holds a seat |
| product optional | string | Product id or slug the key must cover: its own product, an add-on bought against it, or a package its rule includes. A key that does not cover it is refused with 409 rather than accepted |
Check an updates entitlement
/api/v1/kahunacart/licenses/public/updates
Ask whether a key covers a release that came out at a given moment, which is the question an updater asks before it downloads. This route is public and unauthenticated; the key is the credential. released_at is the unix timestamp of the release being asked about, not today's date, and leaving it out answers 422 released_at_required. The boundary is inclusive, so a release put out on the last day of the window is covered. The updates window and the licence term are separate: after the window closes the key still validates as active and the software still runs, and only this check starts saying no. A key that is not usable at all is not entitled either.
Parameters
| Name | Type | Description |
|---|---|---|
| key required | string | The license key, in any spacing, case or separators |
| released_at required | integer | Unix timestamp of the release being asked about |
Get the newest release a key may have
/api/v1/kahunacart/licenses/public/latest
Ask which store-hosted build a key is entitled to, with its version, its changelog, and each file's size, download address and sha256: checksum. This route is public and unauthenticated; the key is the credential. Use it when all that is known is the key; use check_updates when a release date is already in hand. A lapsed updates window is a 200 rather than a refusal: the key still owns what shipped inside its window, so release is the newest build it is entitled to, and release is null only when there is genuinely nothing. status and updates_until come back either way. Unpublished releases are never listed. The download addresses carry no key, and fetching the bytes is not offered as a tool.
Parameters
| Name | Type | Description |
|---|---|---|
| key required | string | The license key, in any spacing, case or separators |
Activate a seat
/api/v1/kahunacart/licenses/public/activate
Take a seat on a license key for one installation. key and instance are required, and instance is whatever uniquely and stably identifies the installation to you: a machine id, the site URL, a container id. It is lower-cased and whitespace-collapsed, so one machine reporting itself two ways does not cost two seats. label is free text shown to the merchant and the customer, and is never matched on.
Release a seat
/api/v1/kahunacart/licenses/public/deactivate
Give a seat back. key and instance are both required, and the answer is {deactivated, seats}. Idempotent by design: releasing an instance that holds no seat answers 200 with deactivated: false rather than an error, because an uninstaller retrying after a dropped connection has to be told the seat is free.
Download a license file
/api/v1/kahunacart/licenses/public/file
The license file itself, with Content-Disposition: attachment and the content type the merchant chose. Takes key and n for which file, 0 by default. Files are generated from the product template and this key rather than read off disk, so a certificate downloaded a year later reflects the licence as it stands then. No file at that position is a 404 with file_not_found.
Download a release build
/api/v1/kahunacart/licenses/public/release
The bytes of one release file. Takes key and file, where file is a file id from a latest answer. The download goes out over the store's own download machinery and honours downloads.method, so xaccel, xsendfile and chunked PHP all work without separate configuration.
Suspended, revoked and expired are answers rather than errors: the body carries valid: false and names the status. Only active sets valid: true.