Guide
Self-service free trials
Hand out time-limited trial keys with no vendor involvement — customers self-serve a trial from your own site, and your app activates it exactly like a paid key.
Keyright can hand out time-limited trial keys for a product with no vendor involvement: a customer clicks “Start free trial” on your site, Keyright mints a trial license, emails them the key, and your app activates it exactly like a paid key. Trials are off by default — a product offers them only once you opt in by setting a trial length.
Throughout, $BASE is your issuing service URL and $TOKEN is a tenant admin token (see Getting started).
1. Enable trials on a product
A trial is defined by three things: days (length; <= 0 disables), tier (the feature tier the trial grants), and seats. Set them on an existing product:
curl -X POST $BASE/admin/products/nebula/trial -H "X-Admin-Token: $TOKEN" -H "content-type: application/json" \
-d '{"days":30,"tier":"enterprise","seats":1}'
# -> { "product":"nebula", "trialDays":30, "trialTier":"enterprise", "trialSeats":1 }
Or set them when creating the product (trialDays, trialTier, trialSeats on POST /admin/products):
curl -X POST $BASE/admin/products -H "X-Admin-Token: $TOKEN" -H "content-type: application/json" \
-d '{"name":"Nebula","slug":"nebula","trialDays":30,"trialTier":"enterprise","trialSeats":1}'
GET /admin/products returns each product’s trialDays/trialTier/trialSeats, and the dashboard’s Products tab exposes the same toggle. Set days to 0 to turn trials back off.
2. A customer requests a trial
POST /v1/trial is public (no auth) and CORS-open, so a browser form on your marketing site can post to it directly:
curl -X POST $BASE/v1/trial -H "content-type: application/json" \
-d '{"product":"nebula","email":"dev@acme.com","company":"Acme"}'
{
"key": "LIC-XXXXXXXXXXXXXXXXXXXX",
"product": "nebula",
"tier": "enterprise",
"seats": 1,
"trial": true,
"expiresUtc": "2026-10-23T12:00:00.0000000Z",
"days": 30,
"created": true
}
The key is also emailed to the address (when SMTP is configured; otherwise the send is logged).
| Field | Required | Notes |
|---|---|---|
product | yes | the product slug |
email | yes | the trial is keyed to this address |
company | no | becomes the licensee name; defaults to the email |
tenant | no | only needed if the same slug exists in more than one tenant, to disambiguate |
One trial per email per product. Re-requesting with the same email returns the same key (created:false) rather than minting a new one — idempotent and abuse-resistant, so a refresh or a second click never resets the clock or stacks trials.
Responses:
| Status | When |
|---|---|
200 | trial issued (or the existing one returned) |
400 | missing/invalid email/product, or the slug exists in more than one tenant (pass tenant) |
403 | trials are not enabled for that product (trialDays <= 0) |
404 | unknown product |
Wire a “Start free trial” button
Because /v1/trial is CORS-open, a static page can call it with fetch:
<button id="trial">Start free trial</button>
<script>
document.getElementById('trial').onclick = async () => {
const res = await fetch('https://keyright.delta1labs.com/v1/trial', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ product: 'nebula', email: userEmail, company: userCompany }),
});
const data = await res.json();
if (res.ok) show(`Your trial key: ${data.key} (expires ${data.expiresUtc})`);
else show(data.error);
};
</script>
3. The customer activates the trial
A trial key activates exactly like a paid key — there is no separate trial code path in your app. With Nebula, for example:
nebula register --key LIC-XXXXXXXXXXXXXXXXXXXX
This performs an online activation (POST /v1/activate), binds the machine, and returns a signed lease with trial:true and the trial’s expiry. The app reflects it in the UI (e.g. “Enterprise Trial — 27 days left”) and, thanks to the cached lease, keeps working offline until the trial expires. At expiry the lease stops validating and the app fails closed to the free/unlicensed state (see .NET SDK integration).
Trial → paid is seamless: when the customer buys and activates a non-trial key, it supersedes the leftover trial with no reinstall.
4. Offline trials for air-gapped evaluators
Self-service trials use the online path, but that is not the only way to trial. For an air-gapped or enterprise evaluator you can also hand out a signed, time-limited offline trial license file — no network required at all. With Nebula’s CLI:
nebula license --sign --trial --tier enterprise --expiry 2026-10-23 \
--licensee "Acme Corp" --private-key ./keys/nebula.private.json --out acme-trial.json
The file carries the trial flag and an expiryUtc; the SDK validates it against the embedded public key and treats it as a trial until it expires. This is the offline analogue of the self-service flow above — same trial semantics, delivered as a file instead of an activation.
See also
- Getting started — issue and verify your first (paid) license.
- .NET SDK integration — how the SDK surfaces
Trial, expiry, and offline grace. - HTTP API reference — the full endpoint map, including
/v1/trial.