Skip to main content

New Licensing mechanism in 3.1.0

· 7 min read
Jeremy Scott
Co-founder

Licensing just leveled up in 3.1.0. The old embedded “you-either-have-one-or-you-dont” license stays supported, but you no longer need to bake a license into every package. Instead, licenses become hierarchical, divisible, and issued out (perhap locally) on demand—perfect for organizations, managed service providers, resellers, and customer-managed setups.

The previous licensing model required you to bake an expiring license into your distributed packages: licensing was a "build time" concern - except when you needed to remember which licenses, in which packages were expiring soon and come up with a redeployment plan - then it was a headache-concern.

How does the new licensing model work?

Licenses now flow down a chain of authority: ROOT → ISSUER → VENDOR → ORGANISATION → PLATFORM → APP.

  • Upstream parties (issuer/vendor) can hold a large pool of credits (e.g., 10,000,000) and carve out child licenses as needed.
  • Organisations receive a license and can split it into platform licenses. These can be installed a into the opscotch licensing app (running on an agent).
  • The licensing app can run as a license server (or embedded on an agent) and serves licenses to apps.
  • When an app is loaded, the agent requests a license. When the app unloads or the agent shuts down, the credits return to the license server license pool.
  • Attributes can be added anywhere in the chain and propagate to children. Attributes can be:
    • Strings
    • Numeric
    • Boolean
    • With constraints (e.g., credits must decrease when carving out child licenses).
      • Read-only
      • Directional (increase/decrease) to enforce how values change downstream.
    • For example:
      • the license issue date is a read only string
      • the license expiry date is a string that can only be decremented (child licenses can have an expiry date less than or equal to the parent)
  • Licenses are scoped per agent type (environment) : development and production
    • You can not use a license development license on a production agent and vice versa

Bootstrap: licensing host and pool

Agents fetch licenses at load time if a license is not embedded in an app:

{
"licenseHost": "http://localhost:39576", // optional; defaults to localhost licensing app
"licenseHostPoolId": "my-dept-license-pool" // optional pool id on the licensing app
}

Point licenseHost to your licensing app; set licenseHostPoolId to the pool that holds your platform license.

What’s new vs old

  • Old (still works): License embedded in the package; single on/off model; required redeploy to update.
  • New: Hierarchical, divisible licenses with multiple roles; authenticated handouts at runtime; attributes that flow down the chain; dynamic credit allocation and return; no package redeploy needed to change licensing.

Roles and flow (summary)

  • ISSUER/VENDOR: Hold a large credit pool, carve out vendor or org licenses; can add metadata (issuer/vendor tags, constraints).
  • ORGANISATION: Receives a license, splits into platform licenses; adds org-specific metadata.
  • PLATFORM: Installed into the licensing app; serves licenses to agents. Can set operational attributes (e.g., phone-home interval, renewal thresholds).
  • APP: Requests runtime licenses from the licensing app; credits are checked out and returned automatically.

Attributes example (conceptual):

  • credits: "1000:ni:" — numeric, must not increment (you can only carve downwards).
  • issuer: "sample-issuer:r:" — read-only string tag.

Example flows (summarized)

Issuer creates a vendor license (from a ROOT parent):

  • POST to /request with licenseTo, parent license/public key, and metadata (issuer, credits, etc.).

Vendor creates an organisation license:

  • POST to /request with vendor license as parent; add vendor tag, adjust credits, pass down flags (e.g., sv as boolean/readonly/directional).

Organisation creates a platform license:

  • POST to /request with org license as parent; set platform metadata (credits, platform name, phone-home interval, renewal thresholds, reissue duration).

Install the platform license into the licensing app (pool id). Agents pointing to that host/pool receive licenses automatically when apps load.

Operational benefits

  • No redeploy to update licensing: change issued licenses without rebuilding packages.
  • Reseller-friendly: issuers/vendors hold big pools and subdivide at will.
  • Customer-managed: organisations split their own credits into platform licenses.
  • Runtime efficiency: credits check out/check in automatically with app lifecycle.
  • Policy-rich metadata: attributes propagate; numeric/read-only/directional tags enforce constraints downstream.
  • Flexible packaging: you can still embed licenses in packages; creators can embed a full license for the app or a partial “subsidy” license (e.g., cover half the required credits) and let the licensing app top up the remainder.

The legacy embedded license path remains if you need it, but the new hierarchical model is the recommended way to manage opscotch licensing from 3.1.0 onward.***

Detailed example

Here is a detailed example of how to create a child license from an issued parent license.

This example is for a organisation parent license creating platform licenses, however the process is identical for any other license type.

The parent license

When working with licenses, you will always have a parent license.

The parent license has these properties:

  • license: a base64 encoded license - whoeever holds this data can use whatever it entitles them to
  • licenseId: a string of the id of the license
  • secretKeyHex: a string of the secret for performing operations on the license - the is secret to people working with licenses i.e. people in your organisation that might divide the organisation license
  • type: the type of license i.e. organisation in this case
  • licensedTo: the entity that the license was issued to
  • metadata: this are attributes (see above) on the license. They will include (amongst others):
    • env: the environment that the license is valid for
    • issuedDateUTC : the date-time that the license was issued in UTC
    • expiryDateUTC: the date-time that the license will expire in UTC
    • credits: the number of credits this license is entitled to issue

The child license

You will use the parent license and the parent license secret to create a child license.

To create a child license you need the following properties:

  • licenseTo: the entity that you are creating a license for
  • parentLicense: the base64 encoded license from the parent license
  • parentSecretKey: the secretKeyHex from the parent license
  • metaData: any optional changes to metadata - likely you will want to set a lower credits for example

Create the child license

In this example, the parent license has these properties:

  • license: AAEURUM1QzVC...QaALxAg== (this has been tructacted)
  • secretKeyHex: 4609DC92...F13A86F6E (this has been tructacted)
  • metaData:
    • credits: "500:ni:16969ADFD7185FBE588E:"
      • this means that credits value is 500, must be a number (n), and can not be increased (i) and this value was set by license id 16969ADFD7185FBE588E

Here are the properties we will use:

  • licenseTo: platform1
  • parentLicense: AAEURUM1QzVC...QaALxAg== (this has been tructacted)
  • parentSecretKey: 4609DC92...F13A86F6E (this has been tructacted)
  • metaData:
    • credits: "100:ni:"
      • this means that credits value is 100, must be a number (n), and can not be increased (i)
    • platform : "sample-platform1:r:"
      • this means we are adding an attribute platform with the value sample-platform1 and its read-only (r)

Here is the curl command:

curl localhost:39576/request -v -H "Content-Type: application/json" -X POST -d '{ "licenseTo" : "sample-platform", "parentLicense" : "AAEURUM1QzVC...QaALxAg==", "parentSecretKey" : "4609DC92...F13A86F6E", "metaData" : { "credits" : "100:ni:", "platform" : "sample-platform:r:" }  }'

You'll get a response like this:

{
"status": 200,
"license": "AAEUQUE1M...peV68pcCjQc=",
"licenseId": "AA52844CF764A2FFAA8C",
"secretKeyHex": "6F6018B...0BBC10F0B",
"type": "ORG",
"metaData": {
"env": "opscotch-development:r:9C3AFAE86079E61A0FB8:",
"issuedDateUTC": "2025-12-09T19:35:54.054Z!d!AA52844CF764A2FFAA8C!",
"expiryDateUTC": "2035-12-07T04:03:28.480Z!i!9C3AFAE86079E61A0FB8!",
"service": "opscotch development!r!9C3AFAE86079E61A0FB8!",
"issuer": "sample-issuer:r:EC5C5B471A2C133C8CF1:",
"credits": "100:ni:AA52844CF764A2FFAA8C:",
"vendor": "sample-vendor:r:16969ADFD7185FBE588E:",
"platform": "sample-platform:r:AA52844CF764A2FFAA8C:"
}
}

The license property can now be added to your opscotch-licensing-app

The opscotch licensing app