Licenses
This page explains how to sell software that needs a key: an application, a plugin, a theme, a library. Each sale produces a key the customer types in, the software checks, and you can suspend, extend or revoke afterwards.
Licensing is an add-on, not part of KahunaCart itself. Install the kahunacart-licenses plugin alongside the base plugin and your store starts issuing keys. Leave it out and nothing on this page applies.
Before you begin
- A working store you can already take an order on. See Getting started.
- The
kahunacart-licensesplugin installed and enabled, and thekahunacart.licensespermission on whoever is going to run it. - The scheduler running. Keys are minted by the job queue when an order is paid, not during checkout.
- Something to license. A digital product with builds attached is the usual case (see Digital products), but any product can carry a key, including a physical one that ships with a printed certificate.
Licensed products
Configure licensing rules under Licenses → Licensed products. Products without a rule continue to sell without issuing keys.
A rule is addressed by what it governs. A rule with no variant covers every variant of the product; a rule naming a variant replaces it for that variant only. This lets you license the "Team" variant on different terms without writing a rule for every other variant.
| Field | What it does |
|---|---|
| Rule kind | license mints keys. renewal extends a key that already exists — see Renewals. addon adds this product to a key the customer already holds — see Add-ons. |
| Keys per item | How many keys one purchased item earns. Usually one. |
| Seats | How many machines a key may be active on at once. Blank is unlimited. |
| Extra seats per item | Seats added per unit bought, for the customer who puts 5 in the box and expects a five-seat key rather than five keys. |
| Term (days) | How long the licence itself lasts. Blank is perpetual. |
| Updates window (days) | How long the purchase keeps earning new builds. Blank is forever. |
| Key prefix | Up to eight letters or digits marking the keys this product issues. |
Every field left blank falls back to the store defaults in Licenses → Settings. That screen is also where you switch licensing on and choose whether keys are minted when payment is received or when the order is completed. On an order paid by deposit the two differ: the order completes when the deposit is paid, but only counts as paid once the balance is, so Order Completed issues the key on the deposit and Payment Received waits for the balance. Each rule also carries its own delivery instructions and licence files, covered in What the customer gets.
Term and updates window are two separate axes
The term determines how long the licence is valid. The updates window determines how long it receives new builds. They are independent dates on the same key.
A key whose updates window has lapsed still reports active and the software still runs; it just stops being offered versions published after the window closed. A key whose term has run out reports expired, and that stops the software. Set a term and no updates window for a subscription-style product. Set an updates window and no term for a perpetual licence with a year of updates.
For the perpetual licence with an updates window, you enforce nothing by hand. KahunaCart asks the licensing plugin every time a customer's versions are about to be listed and again at download time, and the answer narrows the list to the builds that shipped inside the key's window. The boundary is inclusive and compares the release date, not today: a build published on the last day of the window stays downloadable forever, and one published the day after never is. Product releases explains publishing a version and who gets which one.
Note
Releases are a core KahunaCart feature and work without licensing. On a stock store every buyer gets every published version, forever. Licensing narrows that, and an add-on can only narrow it. Nothing here can hand a customer a version the store did not already publish.
What a key looks like
The default is KC-XXXX-XXXX-XXXX-XXXX: a prefix, then four blocks of four characters from an alphabet with 0 O 1 I L left out, so a key read off a screen cannot be mistyped into a different valid key. Four blocks of four is about 79 bits of randomness.
Six fields under Settings → Key format describe it, with a live example key beside them that updates as you type:
| Field | What it does |
|---|---|
| Prefix | The leading marker on every key — up to eight letters or digits, empty for none. |
| Blocks | How many groups the random part prints in, 1–8. |
| Characters per block | How many characters each group holds, 2–12. |
| Separator | What goes between the groups: up to three punctuation characters, or nothing at all for one unbroken run. Letters and digits are refused because they would become part of the key. |
| Alphabet | The characters keys are drawn from, at least eight distinct. |
| Template | The advanced layout, e.g. {prefix}/{block:5}-{block:5}-{block:5}. When it is set it replaces the three fields above entirely. |
A rule can override any of the six for its own product, from the collapsed Key format group in the rule editor. It defaults to "Use the store format". Switch it to "Custom for this product" and anything you leave blank is still inherited, so a rule that only wants a different separator says only that.
A key's identity is its letters and digits, upper-cased, and nothing else. Two things follow:
- Spacing, case and separators never matter when a customer types a key back.
KC-2345-6789-ABCD-EFGH,kc 2345 6789 abcd efghandkc23456789abcdefghare one key, and so is the same string with the dashes turned into slashes by a mail client. - Changing the format only affects keys issued from then on. Existing keys keep working and keep printing the way the customer already has them written down.
What the customer gets
Keys reach the customer in four places, all switched on by enabling the plugin.
The receipt. The order page gains a Your License Keys panel above every other panel. Each key is a card with the product and variant, a status pill, the key in a monospace box with a copy button, seats used against seats allowed, the expiry and the end of the updates window, your instructions, and a download link per licence file. Between paying and the queue draining, the panel says the keys are on their way and how many are coming.
The order email. The same keys go into the order confirmation as a plain-styled partial with everything inline and no copy button. Issuing runs ahead of the confirmation, and if it has not finished the message is held a few seconds at a time rather than going out empty.
The Licenses tab. {base}/account/licenses lists every key a signed-in customer holds, newest first, with where the key is in use. Each live activation is a row with the machine's label, when it was activated, when it was last seen, and a Deactivate button, so a customer whose laptop died gets their seat back without opening a ticket. A one-line card on the account overview counts their licences, the ones expiring within thirty days, and the ones already gone.
Licence files. A rule can generate files a customer downloads per key: a .lic, a certificate, a config fragment. They are generated on demand from a template, not stored, so there is nothing on disk to back up, go stale or leak. Put {signature} in one and the file carries an HMAC your software can check on a machine that has never reached the store.
Delivery copy
Four pieces of merchant-written text travel with a key: the instructions printed beside every key, an email introduction printed once above the keys in an order email, an account page introduction at the top of the Licenses tab, and the licence files themselves. All four are Markdown and take placeholders. All four resolve in two layers: the store default from Settings → What the customer sees, replaced field by field by a product's own rule. A product that only wants its own instructions keeps the store's email introduction.
The placeholders are {key}, {product}, {variant}, {customer_name}, {customer_email}, {order_number}, {expires}, {updates_until}, {seats}, {store_url}, {account_url}, {file_url:0} for the first licence file, and {signature}. The admin prints the legend beside every box and drops a token into whichever field you were last typing in when you click it.
A placeholder nobody recognises is left as written. Somebody who types {licence} sees {licence} on the page and fixes it.
The two introductions print once above every key on the order, so only {customer_name}, {customer_email}, {order_number}, {store_url} and {account_url} resolve there. The key-specific tokens are left as typed. Put the key in the instructions, which print per key.
Every panel above is a Twig partial you can replace by copying it into your theme. Every colour it uses reads a token the base plugin already publishes, so a theme styled for KahunaCart styles the licence panels too. See Theming.
The admin
Licensing appears inside KahunaCart's own admin under Add-ons in the side navigation, at /admin/plugin/kahunacart#/section/licenses, with three tabs.
Licenses is the list. Search matches a fragment of a key, a buyer's email address or an order number, and the status chips narrow it to active, suspended, revoked or expired. Each row carries the key, its product, seats in use against seats allowed, the customer, the order and the two dates, with Copy to put the key on the clipboard and Open for the detail screen.
The detail screen shows everything one key knows about itself. An Edit block covers the seat allowance, the expiry, the updates window, a private note the customer never sees, and a JSON metadata object handed to your software with every check. An Activations table has Free seat beside each machine. What the customer sees renders your delivery copy with this key's own values.
The actions are Suspend (reversible; activations stay in place so reinstating puts the customer back where they were), Reinstate, Revoke (permanent; it asks for a reason that stays on the record), and Regenerate, which revokes the current key and mints a replacement with the same order, customer, product, seats and dates.
Issue a key creates a key without an order, for example for a complimentary licence, a review copy, or a replacement for a customer who bought through another channel. Choose the product, optionally name a customer by email, override the seats, term or updates window, and anything left blank takes the store default. No order is created, so this does not appear in revenue reports.
Licensed products is the rule editor covered above. Settings holds the store-wide defaults, the key format, the public API's switches and rate limits, the delivery copy and the signing secret.
Licensing also draws one panel outside its own screens. Open an order that issued keys and a Licenses panel appears in the column beside it, listing each key with its status, the product, the project name where the customer has given one, and the expiry where the key has one. Every key is a link that opens that key's own record, so answering "where is my key" no longer means copying the order number into the licences search. The same panel draws beside a customer record, which is the quickest answer to what one person has bought a key for. An order or a customer with no keys behind it draws no panel at all — except an order that is going to earn keys and has not yet, which says No keys issued yet and what it is waiting for, so an order sitting on an unpaid bank transfer never looks like licensing has gone wrong. An order with a deposit paid and the balance still owed says Keys are issued once the order is paid in full.
Beside an order, the panel also lists the add-ons that order put on a key, under Add-ons, each with the key it landed on. An add-on mints no key, so without this list an order that only bought add-ons would show nothing.
The panel is an extension zone widget, so you decide where it sits: KahunaCart → Extensions → Zones lists the panels in the column beside an order and beside a customer, with arrows to move them, and Back to the declared order puts everything back the way the plugins asked for it.
Note
Everything here is gated on the single permission kahunacart.licenses. There is no view/manage split: a licence key is the credential, so access to the list reveals the keys. Anyone who can view licences can also revoke them.
Messages to you
Two rows under Notifications, Store owner, go to whatever channels you have ticked, such as Slack, Discord or your phone, or to the ones you chose under New messages go to.
| Row | Priority | When |
|---|---|---|
| A paid renewal or add-on did not land | High | A customer paid for a renewal or an add-on and the line could not be applied. Once per order line. |
| A license is out of seats | Low, switched off until you turn it on | Somebody tried to activate a license with every seat in use. Once per license per day. |
Neither carries a license key, because a chat channel prints what it is given and a key is a credential. The seats message names the product and the customer's name, never their email, and both link to the order or the license.
Warning
The signing secret is written once and shown once, and afterwards only reported as its last four characters. Rotate is the only way it changes. Rotating it breaks every client still pinned to the old one and invalidates every licence file already downloaded.
Add-ons
An add-on is a product that adds itself to a key the customer already holds. They buy it like anything else, nothing new is minted, and the key they have starts covering one more product. This is how you sell a core product and a dozen extensions without the customer holding thirteen keys.
Set one up with two products and one rule:
- The licensed product, whatever you already sell, with an ordinary
licenserule. - The add-on product, priced and published like any other.
- A rule on the add-on product under Licenses → Licensed products, with Rule kind set to
addonand Attaches to which product naming the licensed product.
Seats, term and updates window are ignored on an add-on rule, because the key it attaches to owns them. The editor refuses to save an add-on that attaches to nothing, and the rule table prints Attaches to under each add-on so you can see what it points at without opening it.
When somebody buys an add-on, the product page asks Which project is this for? and the add-on lands on the key they pick. Customers name their keys ("Surf shop", "Client: Acme") on their account's Licenses tab, and the name leads wherever a key is printed. The receipt and the order email print one block per add-on with that rule's instructions, and the licence record in the admin lists everything the key covers under Also covers.
One purchase, several products
An add-on rule can also grant other products on the same key. This is how you sell a bundle as a variant of a product rather than as a product of its own: a "Reports" add-on with a plain variant and a "Reports + Exports" variant, where the bundle variant's own rule also grants Exports.
| Rule | Rule kind | Attaches to | Also grants |
|---|---|---|---|
| Exports (product-wide) | addon |
Your core product | — |
| Reports (product-wide) | addon |
Your core product | — |
| Reports, "Reports + Exports" variant | addon |
Your core product | Exports |
In the rule editor it is the Also grants list under an add-on rule: tick the other products, and the rule table prints Also grants Exports under it. A variant rule replaces the product-wide one for that variant, so the plain variant keeps granting Reports alone.
Saving refuses the list, with a sentence saying why, when an entry names no product, appears twice, or is the rule's own product or the product it attaches to, since both are covered already. Only an addon rule can grant anything, up to 20 products, and switching a rule to another kind drops its list.
Every extra product lands on the same key, against the same order, as the line itself. Running the issuing job again adds nothing, and a product the key already had from an earlier order stays with that earlier order.
Over the API the list is grants, an array of product ids on the rule, stored in the rule's meta_json. GET /kahunacart/licenses/issuance/{orderId} answers addons beside the key counts: each product the order put on a key, with its license_id and key. See Licensing rules.
Add-ons that need another add-on first
Some add-ons only work on top of another one. An add-on rule can say so with a Requires list: products the customer's key must already cover before this add-on can be bought. Paired with a bundle variant, it sells the cheaper variant only to people who can use it. The product-wide rule on the add-on requires the other product, and the bundle variant's own rule grants it instead and requires nothing.
| Rule | Rule kind | Also grants | Requires |
|---|---|---|---|
| Exports (product-wide) | addon |
— | — |
| Reports, "Reports only" variant (product-wide rule) | addon |
— | Exports |
| Reports, "Reports + Exports" variant | addon |
Exports | — |
The requirement is met when either of these holds:
- The key the line lands on already covers it. That is the key picked on the line, or, for "the license in this cart" when that license has left the basket, the newest key the customer holds, the same pick the issuing job makes. What the key covers is what its license card lists: its own product, every add-on granted onto it, and the packages its rule says come included.
- The same basket holds a line for that product, or a line whose rule grants it, such as the bundle variant.
A key being bought in the same basket covers nothing yet beyond its own product, so only the basket can supply the rest.
It is checked when the line goes into the cart, and again over the whole basket at checkout, where taking the required product back out of the basket is caught. The checkout check runs on every road to a payment, the checkout form, the storefront JSON API and the express buttons, so there is no way round it. The other add-on checks come first, so an unknown or revoked key is still told so.
The refusal names the products from your catalog, so nothing is hard-coded. When another variant of the same product grants everything that is missing and requires nothing, the sentence offers that variant by its name. With Widget Pro as the product the add-ons attach to:
| Who | What they read |
|---|---|
| A guest with no key to name | Reports only is for stores that already have Exports. Sign in to the account that holds your Widget Pro license, or choose Reports + Exports. |
| A key without it (signed in, or a guest who typed the key) | Your license doesn't include Exports yet. Choose Reports + Exports to get both. |
Sign in in the guest's sentence is a link. It goes to the store's sign-in page and brings the shopper back to the product page they were on, or to the checkout when that is where the refusal came from, so they can pick the license they already own. On a store without the Login plugin the words stay and the link goes. The link needs KahunaCart 1.2.11 or later.
With no such variant on sale, the way out is or add Exports to your cart as well. for a guest and Add Exports to your cart as well. for a known key. A signed-in customer who picked no key is told Add-ons need a license to attach to, since they have keys to pick from. The four sentences are ADDON_REQUIRES_SIGN_IN, ADDON_REQUIRES_SIGN_IN_ADD, ADDON_REQUIRES_MISSING and ADDON_REQUIRES_MISSING_ADD in the language file. A translation keeps the brackets in [Sign in](%5$s) (%4$s in the _ADD sentence) around whatever it calls signing in, because that is the storefront message link.
In the rule editor it is the Requires list under an add-on rule, beside Also grants, and the rule table prints Requires Exports under the rule. Over the API it is requires, an array of product ids stored in the rule's meta_json. Saving refuses it for the same mistakes as grants, and also when a product is both granted and required. Only an addon rule can require anything, up to 20 products, and switching a rule to another kind drops its list.
A basket holding both parts separately can also be offered the bundle variant instead, when it works out cheaper. That is KahunaCart's bundle suggestions, turned on in Settings, with a Bundle of list on the bundle variant.
Refunds and cancellations
A full refund or a cancellation takes back everything the order added: the line's own product and every extra it granted. Anything the key got from another order stays, so a customer who bought Exports on its own and later refunds a "Reports + Exports" order keeps Exports. The receipt stops listing the reversed add-ons, and a payment webhook that arrives again after the refund cannot grant them back.
Renewals
A renewal is a product that sells more time on a key the customer already holds. It has a price, a variant, a category and a tax class, and the money lands on an ordinary order you report on, refund and reconcile like any other. Nothing new is minted; the key they typed into the buy form gets its dates pushed out.
Setting one up takes two products and one rule:
- Keep the licensed product as it is.
- Add a second product to your catalog, such as "Widget Pro, one more year".
- Give that second product a rule with Rule kind
renewal, Renews which product set to the licensed one, and a term and updates window saying how much time the money buys.
Leave Renews which product unset and the renewal accepts any live key in the store. That is usually a mistake.
To sell several lengths, put them on one product as variants with a rule on each, or do nothing: buying three of a one-year renewal buys three years.
The renewal product's page gains a License key to renew field inside the add-to-cart form. Signed-in customers get a dropdown of their own eligible keys with a last option that opens a text box, for renewing on a colleague's behalf; everybody else types the key. It is checked at add-to-cart, before any money moves, and the customer is told at the field whether the key is unrecognised, for a different product, revoked or suspended. A key that has already run out is accepted. That is the most common reason anybody buys a renewal.
When payment is received, three things happen:
- Time is added, not replaced. The new dates run from
max(now, where the key currently stands)plus the days bought. Renewing early costs the customer nothing, and renewing two years late starts from today. - The old dates are recorded on the order. That record makes a renewal reversible and stops a queue retry from adding the year twice.
- A refund puts the time back to the recorded dates. Reversing an order revokes the keys it minted and restores the ones it renewed. A renewal somebody has already built on (a second renewal since, or dates you edited by hand) is marked superseded and left for a person to judge.
A renewal line that cannot be applied does not fail the job, because that would take down the other lines of the same order. The reason is recorded on the order, and the run carries on. You are told as well, because the customer has paid and has nothing to show for it: A paid renewal or add-on did not land goes to the channels ticked on it under Notifications → Store owner, at high priority, once per order line however many times the job runs. It names the order, the product and the reason in words, and never the key. The same message covers an add-on line that could not attach to a license. See Messages to you.
Every key in the customer's account gains a Renew link as soon as a renewal rule exists for its product, pointing at that renewal product's page. It is computed from the rules, so writing the rule is the whole setup. It reads Renew now once the key has lapsed. Revoked keys never show it, and receipts never carry it.
Note
This is the manual kind of renewal: the customer comes back, buys, and the key gets longer. Automatic renewal, a card charged on a schedule, is the Subscriptions add-on's job. Install it, point a plan's renewal variant at the renewal product you just wrote, and every period extends the key.
The public API
Seven unauthenticated endpoints answer software running on your customer's own machine. They live under the Grav API plugin's base, /api/v1 in a stock install. The licence key is the credential. There is no login.
Send the key as an Authorization: Bearer header wherever you have the choice. A query string lands in your access log, in any proxy in front of it, and in shell history.
Three endpoints cover the software you sell:
POST /validate— is this key good? Answers the status, the product, the seats, the expiry, the end of the updates window, and whatever metadata you put on the key.POST /activate— take a seat for a named installation. Re-activating a machine that already holds one succeeds and costs nothing, so a reinstall never costs a second seat.POST /deactivate— give the seat back. Releasing a seat that is already free succeeds too.
curl -sS https://your-store.example.com/api/v1/kahunacart/licenses/public/validate \
-H 'Content-Type: application/json' \
-d '{"key":"KC-2345-6789-ABCD-EFGH","instance":"studio-01","product":"pro-edition"}'
{
"data": {
"key": "KC-2345-6789-ABCD-EFGH",
"valid": true,
"status": "active",
"product": { "id": 7, "slug": "pro-edition", "title": "Pro Edition" },
"expires_at": null,
"updates_until": 1791536000,
"seats": { "max": 2, "used": 1 },
"activated": true
}
}
Suspended, revoked and expired keys are answers, not errors. They come back 200 with valid: false and a status, so your software can tell the customer something different in each case.
Two more endpoints turn your store into the update server for the builds you publish there. GET /latest answers the newest release the key may have (version, date, notes, and a download URL and sha256: checksum per file), and GET /release sends the bytes of one of them. A lapsed updates window is an answer, not a refusal: latest returns the newest build the key is entitled to along with the date the window closed, so your client can say "renew to get 3.0" instead of "update failed". Product releases covers that flow from the merchant's side, including publishing a build from CI in a single call.
All seven public endpoints
| Method | Path | What it answers |
|---|---|---|
POST |
/kahunacart/licenses/public/validate |
Is this key good, and what does it entitle. |
POST |
/kahunacart/licenses/public/activate |
Take a seat for a named installation. |
POST |
/kahunacart/licenses/public/deactivate |
Give that seat back. Idempotent. |
GET |
/kahunacart/licenses/public/updates |
Does this key cover a release put out on a given date. |
GET |
/kahunacart/licenses/public/file |
The licence file at position n, generated on demand. |
GET |
/kahunacart/licenses/public/latest |
The newest store-hosted release this key may have, with checksums. |
GET |
/kahunacart/licenses/public/release |
The bytes of one file from that answer. |
Refusals carry a stable machine code in the body and in an X-KahunaCart-Error header — license_not_found, product_mismatch, seats_full, rate_limited and a handful more. Branch on the code; the message beside it is written for people and may be reworded at any time.
Three switches under Settings → Public validation API control all of it: whether the API is on, how many requests a minute one key and one address may make, and which browser origins may call it. Leave origins empty and only server-side clients get through. Turning the API off also turns your licence file links into 404s, so put what the customer needs in the instructions instead.
Set a signing secret and every response is signed. A client can then trust an answer received over a connection it does not fully control, and {signature} in a licence file can be verified on a machine that has never reached the store. Downloads themselves are not signed; the checksum in the latest answer is what makes them verifiable.
Configuration reference
Every setting below is store-wide. Edit them at KahunaCart → Add-ons → Licenses → Settings, or in the Grav plugin list at Plugins → KahunaCart Licenses. Both screens write the same file, user/config/plugins/kahunacart-licenses.yaml.
Configure this page once, then override only what a particular product needs from its own rule under Licensed products. A rule that leaves a field blank inherits the value here.
Note
Where the config file cannot be written (an immutable deployment, a container with a baked-in user/), the Settings screen says so and disables its save button.
Issuance
| Config key | Admin field | Default | What it does |
|---|---|---|---|
enabled |
Plugin Status | true |
Turns the add-on off without uninstalling it. With it off no keys are issued and none of the screens on this page appear. |
issue_on |
Issue Keys On | paid |
When a key is minted. paid waits for the money to land. completed issues the moment the order is placed, for bank transfers and invoices where the customer should hold a key before they pay. On an order paid by deposit, completed issues on the deposit and paid waits for the balance. |
defaults.keys_per_qty |
Keys per Item | 1 |
How many keys one unit of a licensed product earns. 0 issues a single key whose seat count grows with the quantity instead — the floating-licence model. 0–100. |
defaults.seats |
Default Seats | 1 |
How many installations one key may activate at a time. Leave empty for unlimited. |
defaults.term_days |
Default Term (days) | empty | How long a key stays valid after it is issued. Leave empty for a perpetual key. |
defaults.updates_days |
Default Updates Window (days) | 365 |
How long a key entitles the customer to new releases. Leave empty to entitle every future release forever. |
Key format
| Config key | Admin field | Default | What it does |
|---|---|---|---|
key.prefix |
Key Prefix | KC |
The leading marker on every key, up to 8 characters. Empty for no prefix. |
key.blocks |
Blocks | 4 |
How many groups the random part of a key prints in, 1–8. |
key.block_length |
Characters per Block | 4 |
How many characters each group holds, 2–12. |
key.separator |
Separator | - |
What goes between the groups: up to 3 punctuation characters, or empty for one unbroken run. Letters and digits are refused. |
key.alphabet |
Alphabet | 23456789ABCDEFGHJKMNPQRSTUVWXYZ |
The characters keys are drawn from, at least 8 distinct. The default leaves out 0, O, 1, I and L. |
key.template |
Template | empty | The advanced layout, e.g. {prefix}/{block:5}-{block:5}-{block:5}. When it is set it replaces blocks, characters per block and the separator entirely. |
Changing any of these affects keys issued from then on. Existing keys keep working. See What a key looks like.
Delivery
All four are Markdown and take the placeholders listed in Delivery copy.
| Config key | Admin field | Default | What it does |
|---|---|---|---|
delivery.email_intro |
Email Introduction | Your license key is below. Keep it somewhere safe. |
Printed once above the keys in the order emails. |
delivery.page_intro |
Account Page Introduction | empty | Printed at the top of the customer's Licenses tab. |
delivery.instructions |
Default Instructions | empty | Printed with every key — how to enter it, where to paste it, what to do next. A product rule replaces it. |
delivery.files |
Default License Files | empty | Text files generated per key and offered for download. Each entry carries a file name, a content type (text/plain when blank) and the file's contents. A product rule replaces the whole list. |
Public validation API
| Config key | Admin field | Default | What it does |
|---|---|---|---|
api.enabled |
Public API | true |
Whether the seven public endpoints answer at all. With it off every one of them returns 404, licence file links included. |
api.signing_secret |
Signing Secret | empty | Signs every API response so your software can prove the answer came from this store. Empty leaves responses unsigned. Rotate in the admin is the only way it changes. |
api.cors_origins |
Allowed Browser Origins | empty | Origins permitted to call the API from a browser, e.g. https://app.example.com. Empty means only server-side and desktop clients get through, which is the usual case. |
releases.checksum_max_mb |
Checksum Size Limit (MB) | 64 |
The largest build the latest endpoint computes a SHA-256 checksum for. Bigger builds report no checksum rather than a slow one. 0 switches checksums off entirely. |
api.rate_limit.per_minute |
Requests per Minute per Key | 60 |
Most calls one address may make about one key each minute. Past it the API answers 429 with a Retry-After header. |
api.rate_limit.per_minute_ip |
Requests per Minute per Address | 120 |
Most calls one address may make about any key each minute. The wider of the two limits, and the one that limits attempts to guess valid keys. |
Advanced
| Config key | Admin field | Default | What it does |
|---|---|---|---|
order_data_key |
Order Data Key | license_keys |
The name of this plugin's corner of an order's stored data, where the keys it issued are recorded. Change it only to avoid a collision with another plugin, and only before your first order — renaming it hides keys already recorded under the old name. |