Start here
Getting started
The end-to-end vendor path for both Keyright Cloud and self-hosted — get an issuing service, create a product and signing key, put the SDK in your app, issue a license, verify it in-app, and go live.
This is the end-to-end path for a vendor adding licensing to an app: get an issuing service, create a product and a signing key, embed the SDK in your product, issue a license, verify it in-app, and go live. It runs the same whatever language you ship in — step 3 links to the SDK for yours (or the raw-HTTP path).
Keyright comes in two deployment modes, and every step below shows both. Throughout, $BASE is your issuing service URL and $TOKEN is your admin credential, sent in the X-Admin-Token header.
Pick your path
Keyright Cloud — we host the issuing service; you sign up and start issuing keys. Self-hosted — you deploy the service in your own infrastructure. The part you build is the same for both: the SDK code, the signed license format, and the public key you ship are identical. Only the service URL your app points at and the admin credential you manage with differ.
1. Get your issuing service
Cloud: create a workspace at keyright.delta1labs.com and sign in. Your workspace is provisioned with a signing key automatically, so you can go straight to step 2. Set $BASE=https://keyright.delta1labs.com; your $TOKEN is a team session token — minted when you sign in, and you can invite teammates (with their own tokens) under Settings → Team.
Self-hosted: pull the public container image, point it at an empty PostgreSQL database, give it the secrets it needs (an admin token, a key-encryption secret) and your Keyright license key, then start it — it builds its own schema on first boot and answers /health when it’s ready. Set $BASE to your instance’s URL (locally that’s http://localhost:8080), and $TOKEN to the KEYRIGHT_ADMIN_TOKEN you generated. The full walkthrough is the Deployment guide, and your instance needs a valid key to run — see Licensing & editions.
Everything from here is the same for both modes; only $BASE and $TOKEN change.
Once you’re signed in you land on the dashboard Home, which tracks your progress through exactly the steps below:
2. Create a product (and an edition), then get a signing key
Create the product first — you can’t generate a signing key or issue a license for a product that doesn’t exist. From the dashboard (Products → New product, then Editions → Add edition — the dashboard calls tiers editions; the API still says tiers), or via the API:
# create the product
curl -X POST $BASE/admin/products -H "X-Admin-Token: $TOKEN" -H "content-type: application/json" \
-d '{"name":"Acme App","slug":"acme-app"}'
# add a tier/edition (seat count + an entitlements template your app reads at runtime)
curl -X POST $BASE/admin/products/acme-app/tiers -H "X-Admin-Token: $TOKEN" -H "content-type: application/json" \
-d '{"name":"pro","seats":3,"entitlements":{"export":"true","max-projects":"10"}}'
Entitlement values are strings on the wire ("true"/"false" for flags, an integer or "unlimited" for limits). Quotas (active licenses, product count) follow the tenant’s plan on Cloud; self-hosted paid editions are unlimited. If you sell updates (a year of updates, one major version, a subscription…), give the edition a coverage policy before you issue — see Release coverage.
The product’s Overview tab gathers all of this — the editions with their seat counts and entitlements, the trial, and the signing key you’ll embed next:
Get a signing key. Licenses and leases are signed with an RSA key whose private half stays server-side; your app embeds only the public key. A key is either per-product or tenant-wide (shared by all your products).
- Per-product (recommended):
curl -X POST $BASE/admin/products/acme-app/signing-key -H "X-Admin-Token: $TOKEN"— the response includes the product’ssigningPublicKey. In the dashboard it’s on the product’s Signing key tab. - Tenant-wide: read it from Developers → Keys, or
GET $BASE/admin/public-key(alsoGET $BASE/admin/account→integration.signingPublicKey).
GET /admin/public-keyandGET /admin/accountreturn the tenant key, which isnulluntil a tenant key exists. On a fresh self-hosted instance with no tenant key set, generate a per-product key (above) rather than embedding a null value. A Cloud workspace already has a tenant key.
Copy the base64 public key of whichever key signs that product’s licenses — you’ll paste it into the SDK in step 3. The dashboard opens in a calm Basic view that covers exactly these steps; switch to the Intermediate or Advanced view when you need everything else.
3. Put the SDK in your product
This is the heart of the integration: embed the SDK, initialize it with your product slug and public key, and gate a feature. The code is identical on Cloud and self-hosted — the only thing that changes between modes is ServiceUrl (your $BASE) and, server-side, which admin token issued the key. The license format and the embedded public key don’t change.
The dashboard writes the first version of this code for you: Developers → Quickstart picks your product and language and prefills the snippet with the product slug, its public key, and this instance’s URL — copy it straight in.
Here’s that minimal integration — install, initialize with the product slug + public key + service URL, activate online, then gate a feature — in each supported language. Set the service URL to your $BASE (the hosted URL on Cloud, your instance URL self-hosted); everything else — the SDK calls, the signed license/lease format, the embedded public key — is identical on Cloud and self-hosted.
.NET — Keyright.NET on NuGet (multi-targets netstandard2.0 and net8.0, so it runs on .NET Framework 4.8 and .NET 6–10):
dotnet add package Keyright.NET
using Keyright.Client;
static readonly KeyrightClient License = KeyrightClient.Initialize(new KeyrightOptions
{
Product = "acme-app", // the product slug from step 2
PublicKeyBase64 = "MIIBIjANBgkq...", // the public key from step 2 — not a secret; it only verifies
ServiceUrl = "https://keyright.delta1labs.com", // Cloud: the hosted URL. Self-hosted: your instance URL. Omit for offline-only.
});
// When the customer enters a key, activate online: binds this machine, consumes a seat, caches a signed lease.
LicenseInfo info = await License.ActivateAsync(customerEnteredKey, ct);
// Every later launch verifies offline against the cached lease — no network round-trip.
LicenseInfo offline = License.Validate();
// Gate a paid feature on an entitlement (not on the edition), so tier changes need no new code.
if (info.IsPaid && License.IsEnabled("export"))
ShowExportCommand();
Node.js — keyright on npm (CommonJS, zero dependencies):
npm install keyright
const { KeyrightClient } = require('keyright');
const license = KeyrightClient.initialize({
product: 'acme-app', // the product slug from step 2
publicKeyBase64: 'MIIBIjANBgkq...', // the public key from step 2 — not a secret
serviceUrl: 'https://keyright.delta1labs.com', // Cloud: the hosted URL. Self-hosted: your instance URL.
});
// When the customer enters a key, activate online (binds the machine, consumes a seat, caches a signed lease).
const info = await license.activate(customerEnteredKey);
// Later launches verify offline against the cached lease — synchronous, never throws.
const { info: offline } = license.validate();
// Gate a paid feature on an entitlement.
if (info.isPaid && info.entitlements.isEnabled('export')) {
showExportCommand();
}
Python — keyright on PyPI (Python 3.8+):
pip install "keyright>=1.1.4"
from keyright import KeyrightClient, KeyrightOptions
license = KeyrightClient.initialize(KeyrightOptions(
product="acme-app", # the product slug from step 2
public_key_base64="MIIBIjANBgkq...", # the public key from step 2 — not a secret
service_url="https://keyright.delta1labs.com", # Cloud: the hosted URL. Self-hosted: your instance URL.
))
# When the customer enters a key, activate online (synchronous).
info = license.activate(customer_entered_key)
# Later launches verify offline against the cached lease; validate() returns (info, source).
offline, source = license.validate()
# Gate a paid feature on an entitlement.
if info.is_paid and info.entitlements.is_enabled("export"):
show_export_command()
Java — com.delta1labs:keyright on Maven Central (Java 17+, no runtime dependencies):
<dependency>
<groupId>com.delta1labs</groupId>
<artifactId>keyright</artifactId>
<version>1.1.4</version>
</dependency>
import com.delta1labs.keyright.KeyrightClient;
import com.delta1labs.keyright.KeyrightOptions;
import com.delta1labs.keyright.LicenseInfo;
KeyrightClient client = KeyrightClient.initialize(
new KeyrightOptions()
.product("acme-app") // the product slug from step 2
.publicKeyBase64("MIIBIjANBgkq...") // the public key from step 2 — not a secret
.serviceUrl("https://keyright.delta1labs.com")); // Cloud: the hosted URL. Self-hosted: your instance URL.
// When the customer enters a key, activate online (synchronous).
LicenseInfo info = client.activate(customerEnteredKey);
// Later launches verify offline against the cached lease.
LicenseInfo offline = client.validateInfo();
// Gate a paid feature on an entitlement.
if (info.isPaid() && info.entitlements.isEnabled("export")) {
showExportCommand();
}
Any language (raw HTTP, no SDK) — the runtime API is plain JSON over HTTP, so Go, Rust, C++, PHP, Ruby… talk to it directly. Activate a machine, then verify the signed lease yourself:
curl -s -X POST "$BASE/v1/activate" -H "content-type: application/json" \
-d '{"key":"LIC-9F3A...","machineId":"<your stable per-machine id>","product":"acme-app"}'
# -> { "status":"ok", "seats":3, "used":1, "lease":"{...signed lease JSON...}" }
Store the lease string verbatim and verify its RSA signature against your embedded public key — rebuild the canonical krlease1|… payload and check it with PKCS#1 v1.5 / SHA-256 — before trusting it. Once it verifies, read tier for the edition and entitlements for per-feature gates, and re-validate periodically (POST /v1/validate) to pick up revocations. Fail closed on any non-ok status or a lease that doesn’t verify. The full byte-level recipe (with a Go example you can port) is Use the HTTP API directly.
Every SDK verifies the signature against your embedded public key, checks machine binding and expiry, caches the lease locally, and re-activates automatically before it expires. All of them fail closed to the free/unlicensed state if verification fails or the key is revoked — and because the five clients verify the same signed format, a mixed-language product line shares one tenant and one public key.
For the complete per-language walkthrough — deactivation, offline/air-gapped leases, trials, node-locking, key rotation and release coverage — follow the full guide for yours:
| Language | Package | Guide |
|---|---|---|
| .NET | Keyright.NET (NuGet) | .NET SDK integration |
| Node.js | keyright (npm) | Node.js SDK integration |
| Python | keyright (PyPI) | Python SDK integration |
| Java | com.delta1labs:keyright (Maven) | Java SDK integration |
| Anything else (Go, Rust, C++, PHP…) | — | Use the HTTP API directly |
What’s different Cloud vs self-hosted? Only the base URL and the admin token. Set ServiceUrl to the hosted URL for Cloud or to your instance’s URL for self-hosted; everything else — the SDK calls, the license and lease format, embedding the public key — is the same. See Feature gating for entitlements and limits in depth.
4. Issue a license and verify it in-app
Issue a paid license from Licenses → Issue license in the dashboard, or over the API (omit id to auto-generate the key; include it to import an existing one):
curl -X POST $BASE/admin/licenses -H "X-Admin-Token: $TOKEN" -H "content-type: application/json" \
-d '{"licensee":"Acme Inc.","product":"acme-app","tier":"pro","seats":3,"email":"owner@acme.com"}'
# -> HTTP 201
# { "id":"LIC-XXXX...", "licensee":"Acme Inc.", "product":"acme-app", "tier":"pro",
# "seats":3, "used":0, "expiryUtc":null, "revoked":false, "trial":false, "createdAt":"..." }
# The "id" is the key you give the customer.
The customer pastes that key into your app, which calls ActivateAsync (online) — it binds the machine, consumes a seat, and caches a signed lease so later Validate() calls succeed offline. Machine binding is automatic: Activate takes only the key; the SDK derives a stable machine id from the host, so a single-seat key can’t run on two machines (the raw POST /v1/activate body carries { key, machineId, product } — the SDK fills machineId for you). What you read back from LicenseInfo:
IsPaid/IsTrial— whether it’s a paid or trial license (both activate through the same path).IsEnabled("flag")/GetLimit("name", fallback)— entitlement checks that fail closed.DaysRemaining/ExpiryUtc— for a “27 days left” badge;StatusBadgefor a label like “Pro Trial”.
As each machine activates, it appears on that license’s Devices tab, consuming a seat — you can see which devices are bound, free a seat, and read the entitlements the license carries:
Full activation and offline paths are in Activation & seats and Offline & air-gapped.
Getting a trial key. If you enabled a self-service trial on the product (Products → Trials), your app obtains a trial key by calling the public trial endpoint directly — there’s no SDK wrapper for issuing a trial:
curl -X POST $BASE/v1/trial -H "content-type: application/json" \
-d '{"product":"acme-app","email":"user@customer.com","company":"Customer Ltd"}'
# -> { "ok":true, "product":"acme-app", "trial":true, "emailed":true, "expiresUtc":"...", "days":14, "created":true }
The endpoint emails the trial key to the address (there’s no key in the response — it needs SMTP configured on the instance); the customer then enters that key and passes it to ActivateAsync() like any other key. It activates and validates exactly like a paid key; info.IsTrial is true and DaysRemaining counts down. Full flow: Self-service trials.
5. Go live
Both modes
- The public key embedded in the shipping build matches the key that signs that product’s licenses.
- The
Productslug in the SDK matches the product you issue licenses for (the server scopes activation by product). - Decide offline vs online (or both): an offline file for air-gapped/enterprise, online activation for seat enforcement and revocation — see Offline & air-gapped.
- Seats and expiry set correctly on the edition/license; test hitting the seat limit.
- Test the revocation path (
POST /admin/licenses/{id}/revoke) — the app should drop to free on the next lease refresh. See Revoke, renew & expiry. - If you enforce release coverage, backfill every license issued before coverage (Releases & coverage → Enforcement readiness) before shipping a
KeyrightRequireCoveragebuild, and setKeyrightCoverageVersion/KeyrightCoverageEntitlementDatefrom your release pipeline — Release coverage.
Cloud only
- If you sell via a store / Merchant-of-Record, wire fulfillment to
POST /webhooks/fulfill/{slug}so paid orders auto-issue keys — Store fulfillment. - Confirm your plan covers your expected active-license and product counts (upgrade under Settings → Plan & billing).
Self-hosted only
- Have a backup plan for the database and your key-encryption secret — losing that secret means losing every signing key. Back up your own instance license too.
- Have a signing-key rotation plan in place.
- Keep a valid instance license installed — a self-hosted instance is fail-closed and stops serving runtime calls once its own license lapses past grace (Licensing & editions).
The API at a glance
| Area | Endpoint | Auth | Purpose |
|---|---|---|---|
| Runtime | POST /v1/activate | none | Activate a key on a machine → signed lease. Body: { key, machineId, product } |
| Runtime | POST /v1/validate | none | Validate/refresh without consuming a seat. Body: { key, machineId, product } |
| Admin | GET/POST /admin/products, /admin/products/{slug}/tiers | admin token | Manage products & tiers |
| Admin | POST /admin/products/{slug}/signing-key | admin token | Generate/rotate a product’s signing key → signingPublicKey |
| Admin | POST /admin/licenses, /admin/licenses/{id}/{revoke,renew,transfer,deactivate,offline-lease} | admin token | Issue & manage licenses |
| Admin | GET /admin/licenses, /admin/overview, /admin/licenses/{id}/activations | admin token | Reporting |
| Admin | GET /admin/self-license | admin token | Self-hosted only: this instance’s license status (edition, phase, days left) |
| Admin | GET/POST /admin/team | owner token | Team members |
| Runtime | POST /v1/coverage | none (license key) | Is version X covered for this license? — the building block of your own download gate |
| Admin | POST /admin/products/{slug}/releases, /admin/licenses/{id}/coverage/{preset,grants}, /admin/products/{slug}/coverage/{readiness,backfill,export,migrate} | admin token | Release coverage: registry, terms, backfill, air-gap export |
| Platform | GET/POST /platform/tenants, /platform/plans, /platform/tenants/{id}/signing-key | platform token | Multi-tenant operator |
| Auth | POST /auth/login → /auth/mfa, /auth/session, /auth/sso | — | Dashboard sign-in → session token |
The runtime bodies use machineId (a stable per-machine fingerprint) and product (the product slug) alongside the license key — unrecognized field names are treated as an unknown key. Full request/response details per language are in the SDK and HTTP API guides; coverage endpoints are documented in Release coverage.
The full endpoint map is in the HTTP API reference.