Automate licensing from your own backend.
The Vendor API is how your systems talk to Licentry: issue keys when an order is paid, reset a customer's HWID from your support panel, suspend on a chargeback. Everything the dashboard does, your code can do, scoped to exactly what each key is allowed to touch.
BASE is your Licentry API origin (https://api.licentry.com on Cloud, or your own host when self-hosting). Every path below is relative to it. This is the vendor management API; the client-facing session endpoints your app ships with live in the integration docs.
Authentication
Create an API key in your dashboard under API keys. Each key is shown once, is tied to your account, and carries only the scopes you grant it. Send it as a bearer token:
Authorization: Bearer lk_live_9fA2x…
Keys look like lk_live_ followed by 40 characters. Only a hash is stored, so a leaked database does not expose usable keys. Rotate or revoke a key from the dashboard at any time; a revoked key stops working immediately.
API keys are for server-to-server use. Never ship one in a client binary or web page. The build token and session endpoints, not an API key, are what your distributed app uses.
Scopes
Grant each key the least it needs. A key that only resets HWIDs cannot issue licenses or read billing.
| Scope | Grants |
|---|---|
licenses.read | List and read licenses. |
licenses.issue | Generate new license keys. |
licenses.manage | Revoke, suspend, resume, extend, delete. Implies read. |
devices.read | List devices bound to a license. |
devices.reset | HWID reset and unbind a device. Implies device read. |
products.read | List products. |
products.write | Create and edit products. Implies read. |
analytics.read | Read usage counts. |
* | Full access. Use sparingly. |
A call missing the required scope returns 403 insufficient_scope with the scope it needed. Account-management actions (minting keys, rotating signing keys, billing) are never available to an API key; they require a dashboard sign-in.
Account
Your account, current plan, and usage against plan limits. Also returns your per-account JWKS URLs.
{
"vendor": { "id": "…", "email": "you@studio.com", "planId": "studio", "status": "active" },
"plan": { "id": "studio", "maxActiveLicenses": 5000, "maxProducts": 15, "maxApiKeys": 20 },
"usage": { "activeLicenses": 812, "products": 3, "apiKeys": 4 },
"jwks": {
"offlineGrace": "/v1/vendor/keys/…/offline-grace-jwks",
"responseSig": "/v1/vendor/keys/…/response-sig-jwks"
}
}
Products
A product is the app you license. Its slug is stamped onto every key and is what your client sends to activate.
List your products. Scope products.read.
Create a product. Scope products.write.
curl -sS -X POST "$BASE/v1/vendor/products" \
-H "Authorization: Bearer $LICENTRY_KEY" \
-H "Content-Type: application/json" \
-d '{ "slug": "desktop-app", "name": "Desktop App", "maxDevicesPerKey": 2 }'
Licenses
Issue one or more keys for a product. Scope licenses.issue. The plaintext keys are returned once; only hashes are stored.
curl -sS -X POST "$BASE/v1/vendor/licenses" \
-H "Authorization: Bearer $LICENTRY_KEY" \
-H "Content-Type: application/json" \
-d '{ "product": "desktop-app", "count": 3, "validDays": 365, "note": "order #1024" }'
{
"keys": [
{ "id": "…", "licenseKey": "aB3xK9m-7Qp2n-M4kL8z-Xy1Wq9r", "product": "desktop-app", "validUntil": "2027-07-24T…" }
],
"notice": "Store these keys now; only their hashes are kept."
}
List licenses. Scope licenses.read. Query: product, status (active/suspended/revoked), limit, offset.
One license. Scope licenses.read.
Lifecycle actions. Scope licenses.manage. Revoke and suspend invalidate live sessions on the next heartbeat; resume credits the paused time back onto the expiry; extend takes { "days": n } (negative to shorten).
Permanently delete a license row. Scope licenses.manage.
Devices & HWID
List the machines bound to a license, with first/last IP and last-seen. Scope devices.read.
Clear every bound device so the license can activate on a fresh machine. Scope devices.reset. This bumps the revocation version and ends live sessions on the old machine at its next heartbeat, exactly like the dashboard button and your support team's most common request.
curl -sS -X POST "$BASE/v1/vendor/licenses/$LICENSE_ID/hwid-reset" \ -H "Authorization: Bearer $LICENTRY_KEY"
Unbind a single device without resetting the whole license. Scope devices.reset.
Public JWKS
Every account has its own signing keys, so your app pins keys that only you can sign with. The same isolation applies to license keys at rest: each account's keys are hashed under its own sealed pepper, never a shared secret, so nothing outside your tenant can verify or forge them. These endpoints are public (no auth) and return a standard JWK set.
Find your vendorId and the exact URLs on the Signing keys page of your dashboard. Pin them in your client, or fetch once and cache. Rotating a key from the dashboard serves the new public key here.
Errors & limits
Errors are JSON with an error code and, where useful, a human message.
| Status | Code | Meaning |
|---|---|---|
| 401 | unauthorized | Missing or invalid API key. |
| 403 | insufficient_scope | Key lacks the required scope (returned in required). |
| 403 | vendor_suspended | Account suspended; settle billing to restore access. |
| 403 | plan_limit | A plan quota (licenses, products, keys) was reached. |
| 404 | not_found | No such resource under your account. |
| 429 | rate_limited | Back off; honour the RateLimit-* headers. |