Managing licenses from your server
If you bill outside Stripe, through your own checkout, invoices or a reseller, keep KeyGrant in sync from your backend: issue a license on a sale, extend it on a renewal, and revoke it on a refund. Every request, field and error is in Server endpoints.
… stands for /v1/licenses/:key.
| When | Call |
|---|---|
| A sale | POST /v1/ |
| Find a customer's keys | GET /v1/ |
| A buyer lands on your page after a Stripe or Paddle checkout | GET /v1/ |
| A renewal | POST …/ |
| A refund | POST …/ |
| Free a device | DELETE …/ |
| More or fewer devices | POST …/ |
| Other versions | POST …/ |
| A release of your app | POST /v1/ |
Authenticate
-
Create a secret key on the dashboard's API keys page and send it as
Authorization: Bearer <secret>. -
A
kg_test_key issues test licenses and can access only those (see Testing your integration). - Each secret key has its own request limits (see Rate limits).
Warning
Keep the secret key on your server and never ship it in your app. A kg_live_ key can
access every license in your account.
Issue on a sale
Call POST /v1/licenses with the product's API ID and the modelId of the
license model that was bought. The model sets the expiry, device limit, grace period,
Offline days, entitlements, versions and price.
curl -X POST https://api.keygrant.dev/v1/licenses \ -H "Authorization: Bearer $KEYGRANT_SECRET" \ -H "content-type: application/json" \ -H "Idempotency-Key: $ORDER_ID" \ -d '{"product":"my-product","modelId":"mod_…","email":"[email protected]","version":"2.4.1","sendEmail":true}' { "key": "MYPROD-9K4T-2M8P-XQ7V-H3DW", "emailed": true } The Issue license form's Do this over the API panel shows the request with your real IDs. The options you will use most:
Issue that many keys as one sale, each with the same terms. A request issues one key otherwise, whatever the model's Keys per sale.
sendEmailmails them together in one email.- The response lists them all as
keys, first to last.
Your order ID, as this header or as idempotencyKey in the body. A retry with the same idempotency key returns the original license key with a 200 instead of creating a second one.
- A retry must name the same product and
count, with a secret key of the same mode (live or test), or it returns409 idempotency-key-reused. Its other fields are not compared.
The version being sold, for a model on Version sold or Sold and later. Without it, the key's first activation sets the version.
To give one key its own versions, send minMajor and maxMajor together, and upgradeMonths for its upgrade protection.
true emails the key with the product's delivery template, only when the key has an email, the product's License delivery tab is set to KeyGrant sends them, and its Key delivery email is on (see Key delivery email).
In the response: whether this request sent the email. A retry that returns the original key sends none, so its emailed is false.
Look up licenses
# One license, by its keycurl https://api.keygrant.dev/v1/licenses/MYPROD-9K4T-2M8P-XQ7V-H3DW \ -H "Authorization: Bearer $KEYGRANT_SECRET"
# What one customer holds, newest firstcurl "https://api.keygrant.dev/v1/[email protected]&status=active&limit=25" \ -H "Authorization: Bearer $KEYGRANT_SECRET" - The first request returns
{ license }. - The second returns
{ licenses, nextCursor }. - The
emailfilter takes the full address and ignores case; partial addresses do not match.
A license is described in The license object.
After a checkout
To show the buyer their key on the page Stripe sends them to:
-
Point the Payment Link's success URL at your page with
?session_id=.{CHECKOUT_SESSION_ID} - Request the license from your server with that ID.
- Retry for a few seconds on
404 unknown-checkout.
For a Paddle checkout, request it with the transaction ID (txn_…) that Paddle.js gives on
checkout.completed, in the same call (see
Selling through Paddle).
curl https://api.keygrant.dev/v1/checkouts/cs_test_a1b2c3/license \ -H "Authorization: Bearer $KEYGRANT_SECRET" { license } once the key exists. A checkout that created several keys also returns every one of them, first to last, as licenses.
Not yet. The payment event can reach KeyGrant a moment after the buyer reaches your page, so retry for a few seconds.
KeyGrant held the sale: no key is coming, since you apply it to the key it was for or refund it. Tell the buyer you will be in touch (see Held sales).
An upgrade checkout returns the key it changed only when the checkout named that key. Otherwise it answers 404 with a reason of its own (see Server endpoints, checkout), on which a page that polls may stop.
- Check that
license.productis the product your page sells. - Use a
kg_live_secret key, since a test secret key can access only test checkouts.
Extend on a renewal, revoke on a refund
# A renewal: move the expiry to the end of the new period (Unix milliseconds)curl -X POST https://api.keygrant.dev/v1/licenses/MYPROD-9K4T-2M8P-XQ7V-H3DW/extend \ -H "Authorization: Bearer $KEYGRANT_SECRET" \ -H "content-type: application/json" \ -d '{"expiresAt":1822435200000}'
# A refundcurl -X POST https://api.keygrant.dev/v1/licenses/MYPROD-9K4T-2M8P-XQ7V-H3DW/revoke \ -H "Authorization: Bearer $KEYGRANT_SECRET" \ -H "content-type: application/json" \ -d '{"reason":"refund"}' -
extendtakes the new expiry as an absolute time, so a retried call lands on the same expiry instead of adding the period twice. extendnever shortens a license, and it cannot bring back a revoked one.-
revoketakes areasonofrefund,chargeback,subscription_canceledormanual(the default). The key stops working at the app's next check-in.
Report a renewal before the key ends. Up to a day after a subscription key's grace period ends or a trial key expires, KeyGrant revokes it.
Free a device
DELETE /v1/licenses/:key/activations/:id frees one device's seat. To find the activation ID:
-
GET /v1/licenses/:key/activationslists a license's devices with their activation IDs. -
The
device.activatedwebhook carries it asactivationId. - So does the
aidclaim of that device's lease.
Change a key's device limit
# The customer bought two more devices: 3 becomes 5curl -X POST https://api.keygrant.dev/v1/licenses/MYPROD-9K4T-2M8P-XQ7V-H3DW/devices \ -H "Authorization: Bearer $KEYGRANT_SECRET" \ -H "content-type: application/json" \ -d '{"deviceLimit":5}' - The key gets that limit in place of its model's; the model and its other keys keep theirs.
- Lowering it releases no device: the ones already active keep working, and new ones are refused until fewer than the limit are active.
Change a key's versions
# Let a 1.x key run 2.x and every later version toocurl -X POST https://api.keygrant.dev/v1/licenses/MYPROD-9K4T-2M8P-XQ7V-H3DW/versions \ -H "Authorization: Bearer $KEYGRANT_SECRET" \ -H "content-type: application/json" \ -d '{"minMajor":1,"maxMajor":null}'
Send both bounds every time; maxMajor null means "and later". The app
picks up the change at its next check-in.
Report a release
# In your release script, with a live secret keycurl -X POST https://api.keygrant.dev/v1/products/my-product/releases \ -H "Authorization: Bearer $KEYGRANT_SECRET" \ -H "content-type: application/json" \ -d '{"version":"3.1.4"}'# {"product":"my-product","currentMajor":3} This call moves your product's current version up to the major you send.
- It only moves up, never down, so call it on every release, including a hotfix on an older branch.
- Call it before you publish the build, so that upgrade protection can keep the new version for the first customers who run it.
- Test secret keys cannot call it.