Skip to content

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

FieldRequiredNotes
productyesthe product slug
emailyesthe trial is keyed to this address
companynobecomes the licensee name; defaults to the email
tenantnoonly 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:

StatusWhen
200trial issued (or the existing one returned)
400missing/invalid email/product, or the slug exists in more than one tenant (pass tenant)
403trials are not enabled for that product (trialDays <= 0)
404unknown 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