Vendor API

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.

i

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 header
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.

ScopeGrants
licenses.readList and read licenses.
licenses.issueGenerate new license keys.
licenses.manageRevoke, suspend, resume, extend, delete. Implies read.
devices.readList devices bound to a license.
devices.resetHWID reset and unbind a device. Implies device read.
products.readList products.
products.writeCreate and edit products. Implies read.
analytics.readRead 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

GET/v1/vendor/me

Your account, current plan, and usage against plan limits. Also returns your per-account JWKS URLs.

200 OK
{
  "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.

GET/v1/vendor/products

List your products. Scope products.read.

POST/v1/vendor/products

Create a product. Scope products.write.

shell
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

POST/v1/vendor/licenses

Issue one or more keys for a product. Scope licenses.issue. The plaintext keys are returned once; only hashes are stored.

shell · issue 3 one-year keys
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" }'
201 Created
{
  "keys": [
    { "id": "…", "licenseKey": "aB3xK9m-7Qp2n-M4kL8z-Xy1Wq9r", "product": "desktop-app", "validUntil": "2027-07-24T…" }
  ],
  "notice": "Store these keys now; only their hashes are kept."
}
GET/v1/vendor/licenses

List licenses. Scope licenses.read. Query: product, status (active/suspended/revoked), limit, offset.

GET/v1/vendor/licenses/:id

One license. Scope licenses.read.

POST/v1/vendor/licenses/:id/revoke
POST/v1/vendor/licenses/:id/suspend
POST/v1/vendor/licenses/:id/resume
POST/v1/vendor/licenses/:id/extend

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).

DELETE/v1/vendor/licenses/:id

Permanently delete a license row. Scope licenses.manage.

Devices & HWID

GET/v1/vendor/licenses/:id/devices

List the machines bound to a license, with first/last IP and last-seen. Scope devices.read.

POST/v1/vendor/licenses/:id/hwid-reset

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.

shell · reset a customer's HWID
curl -sS -X POST "$BASE/v1/vendor/licenses/$LICENSE_ID/hwid-reset" \
  -H "Authorization: Bearer $LICENTRY_KEY"
DELETE/v1/vendor/licenses/:id/devices/:deviceId

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.

GET/v1/vendor/keys/:vendorId/offline-grace-jwks
GET/v1/vendor/keys/:vendorId/response-sig-jwks

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.

StatusCodeMeaning
401unauthorizedMissing or invalid API key.
403insufficient_scopeKey lacks the required scope (returned in required).
403vendor_suspendedAccount suspended; settle billing to restore access.
403plan_limitA plan quota (licenses, products, keys) was reached.
404not_foundNo such resource under your account.
429rate_limitedBack off; honour the RateLimit-* headers.