Skip to main content

Licentra's own plans for vendors

Licentra is a SaaS product. Its owner (the platform owner) sells it to vendors — App Owner organizations that license their own apps through it — on Free, Starter and Professional plans. This document is the design; the status section at the end says what is built.

1. Licentra licenses itself​

Licentra's plans are ordinary data in its own catalog, created in its own portal:

  • a product, e.g. code licentra (SaaS), owned by the platform owner's app_owner organization;
  • plans free, starter, professional on that product, each with the feature grants in §2;
  • one policy per plan (§5).

Every vendor holds a license on that product (client_org_id = the vendor's organization). services.EntitlementService reads it in-process — no HTTP call, no Gate — and the admin handlers check it before the vendor creates more.

Who is checked​

WhoChecked?
Platform admin (super_admin)Never
Platform owner organization (owns the self product)Never
Vendors (app_owner organizations)Against their license on the self product
Resellers, client organizationsNever — they aren't Licentra's customers. Each reseller user takes a reseller seat on the plan of every vendor that authorizes the reseller (max_reseller_users)

A tenant can be a reseller and a vendor at once: they are two organizations (organizations is unique per tenant and type). The vendor organization's plan, or lack of one, never affects the reseller organization. Calls made while acting as the reseller (X-Organization-Id naming it) are not metered against the vendor plan.

The deployment itself is never licensed: Licentra is the licensor, so nothing above it has to license it. That is what avoids the chicken-and-egg problem on an empty deployment.

Switching it on​

SettingDefaultMeaning
License__SelfProductCodeemptyThe self product's code. Empty = self-licensing off: every check passes, exactly as before.
License__SelfDefaultPlanCodefreeThe plan a new vendor gets at signup.

If SelfProductCode names a product that doesn't exist, the server logs a warning and checks stay off — a typo or half-finished setup can't lock anyone out.

First-time setup​

  1. Sign in with an address in License__PlatformAdminEmails → super_admin.
  2. Create the platform owner organization (app_owner).
  3. Create the self product (licentra, SaaS) and its features — the codes in §2.
  4. Create the plans and their feature grants; one of them must have the code in SelfDefaultPlanCode.
  5. Create the per-plan policies (§5).
  6. Set License__SelfProductCode and restart. New vendors get the default plan at signup; issue licenses to existing vendors from the normal Issue License screen.

2. Plans and feature codes​

A feature missing from a vendor's license is not included (a limit of 0). A limit feature with no value is unlimited.

Feature codeFreeStarterProfessionalChecked when
API requests / month (plan quota, not a feature)1,00010,0001,000,000Every API-key call (phase 2)
max_products1310Creating a product
max_vendor_users1320Granting a role to a new member
max_api_keys1110Creating an API key (live keys counted)
max_reseller_users0325Authorizing, inviting or reinstating a reseller, accepting a reseller invite, granting a reseller role. Counts distinct users with a reseller role in reseller organizations holding a non-revoked authorization on any of the vendor's products
max_activations_per_license110unlimitedCreating/updating a plan on a self-hosted product
max_active_licenses1001,00010,000Issuing a license (phase 2)
node_locked✓✓✓Creating a self-hosted product
key_based✓✓✓— (every license is key-based)
offline_licensing–✓✓Setting a product's offline grace days above 0
basic_analytics–✓✓Analytics endpoints (phase 4)
usage_metering––✓Giving a plan an API-call quota
immutable_audit––✓Audit verification/export, longer retention (phase 4)

The numbers are proposals. They are plan data, edited in the portal, not code.

Development environments were dropped. Vendors test with nfr licenses: they're excluded from activated-user counts and analytics but still count toward the API-request quota (otherwise "test" licenses would carry free production traffic). Keep them short-lived; they are signed with the product's production key.

3. Behaviour​

  • A refused request returns 403 {"error":"plan_limit","feature","limit","used","plan","detail"}; the portal turns it into an upgrade prompt.
  • Downgrades never delete anything. What already exists keeps working; the vendor just can't create more.
  • A vendor without a usable plan (none, expired, revoked, suspended) is read-only: it can sign in and see everything, can't create, and its customers' check-ins and activations are never blocked.
  • Signup without a default plan still succeeds (logged); the vendor is read-only until a platform admin issues a plan.
  • The vendor's plan license is chosen as: usable (active/grace), then internal > paid > nfr > trial, then latest expiry.
  • Entitlements are cached per organization for one minute.

4. Activated users (end users of vendors' apps)​

"Activated users" means the people using a vendor's app (seat subjects), not vendor staff in the portal.

  • Activated = used the app at least once → new license_seats.first_seen_at.
  • Monthly active = seen in the period → license_seats.last_seen_at (the column exists but nothing writes it today) and seat_usage_daily.
  • Reporting: the shared Go client buffers the users its Gate sees and sends them with usage calls, check-ins, or a batched POST /v1/licenses/{key}/seats/seen; offline installs send them at the next check-in.
  • Shown on a license's "Users" tab and the vendor dashboard (basic_analytics). Pseudonymous subject ids; follow the data-retention policy.
  • Open: whether monthly active users becomes a billed plan limit or stays report-only.

5. API-call thresholds and rate limits live in the policy​

New license_policies fields, copied onto each license like grace/overflow:

FieldMeaning
rate_limit_per_user_per_secMax calls per second per end user; null = unlimited
rate_limit_burstShort burst allowance
quota_alert_thresholdsPercentages that notify the vendor, e.g. {80,100} — once per threshold per period
quota_overage_pctHow far past 100% calls still pass
quota_overage_modeblock / allow / bill (replaces the per-license hard_limit flag)
  • Policies per plan: license_policies gains an optional plan_id; resolution order is reseller+plan → plan → reseller → product default.
  • Per-user rate limit is enforced mainly in the consuming app: the token carries rl: {ups, burst} and the client's Gate keeps a token bucket per user (it needs a hook that names the user). Licentra also limits per (license, subject) on calls that name a user, in memory per instance. For the self product the "user" is the vendor's API key.
  • Check-ins and activations are counted but never hard-blocked — blocking them would break the vendor's customers' running apps.
  • Responses carry X-Quota-Limit/Used/Remaining, X-Quota-Status: ok|warning|overage, and 429 + Retry-After when refused.

6. Changing a policy updates existing licenses​

Policy-derived license fields become live; plan-derived ones (seats, quota, activations, features) stay a snapshot — the customer paid for those, and the plan-change action is how they move. This replaces the "frozen at issue" rule in the license lifecycle guide.

  • Saving a policy bumps its version and queues a propagation job (preview: "affects N licenses").
  • Affected: licenses on the policy, plus licenses that now resolve to a different policy (a new plan-specific policy, or one deleted/deactivated).
  • The job runs in batches: re-apply the policy fields, bump token_version, re-sign, audit. Progress is visible and resumes after a restart.
  • A grace period already running is never cut short; overflow seats already granted run out on their own schedule.
  • Trial settings apply to new trials only.
  • Online apps pick up the change at their next check-in (token carries policy_version); offline installs keep the old values until they check in.