Licensing
Licensing in opscotch uses time-limited issued licenses and a delegated license hierarchy.
Licensing Model
Issued licenses form this hierarchy:
ROOTISSUERVENDORORGPLATFORMRUNTIME
Licenses can be split into child licenses down the hierarchy.
In practice:
- end user organisations are issued at the
ORGlevel - the runtime will only accept
RUNTIMEorPLATFORMlicenses ISSUER,VENDOR, andORGparties can use the opscotch licensing service to manage and split licenses- all issued licenses are created through
https://licensing.opscotch.co
An organisation will create PLATFORM licenses. This may be one platform license for a whole environment, or one license per development user.
The previous embedded-license model still works, but it is now the legacy path and is planned for removal in 3.2.0.
All issued licenses have an expiry date. After the expiry date:
- the license becomes invalid
- workflows or apps using that license will stop
- the license must be renewed or replaced before the workload can continue
Roles and Flow
The delegated licensing flow is:
ISSUERandVENDORparties can hold large parent licenses and issue child licenses downstreamORGparties can createPLATFORMlicenses for departments, environments, or individual development usersPLATFORMlicenses can be installed into the opscotch licensing app and used to serve runtime licensingRUNTIMElicenses are the licenses consumed by the runtime when workloads start
This model supports reseller, managed-service, and customer-managed operations without requiring each package to carry its own long-lived embedded license.
License Attributes
License metadata is attribute-based. Attributes are inherited by child licenses unless changed subject to the attribute rules.
Attributes can be:
- string values
- numeric values
- boolean values
- constrained values
Constraint examples include:
- read-only attributes
- directional attributes that only allow values to move in one direction downstream
For example:
issuedDateUTCis a read-only string attributeexpiryDateUTCcan only be decreased when issuing a child licensecreditsis numeric when present and can only be reduced in child licenses
Not all licenses use credits. Some licenses are open and do not carry a credit balance.
Attribute Value Format
Each license attribute uses a specific format that encodes its value, constraints, and change history:
value<delimiter>properties<delimiter>changeBy<delimiter>
For example:
platform:name:r:CF06EC1DA6FAA54E23F7:- a read-only (r) stringexpiryDateUTC:2030-03-13T22:41:27.014Z!i!- a decrease-only (i) datecredits:1000:d:6EC70AFFE6054B4DFA36:- a decrease-only (d) numeric value
The system automatically selects a delimiter (from :!@#$%^&*~|<>?=+) that does not conflict with the attribute value.
Property Constraints
The properties field uses single-character codes to define how an attribute can be modified when creating child licenses:
| Property | Name | Behavior |
|---|---|---|
r | Read-only | The value cannot be changed in child licenses. Any attempt to modify a read-only attribute will be rejected. |
n | Numeric | The value must be a valid number. Enables numeric validation rules. |
i | Increase-only | The new value must be greater than or equal to the existing value. Prevents decreasing the attribute. |
d | Decrease-only | The new value must be less than or equal to the existing value. Prevents increasing the attribute. |
p | Positive | (Numeric only) The new value must be a positive number. |
Constraint Interactions
When both existing and new attribute values specify constraints, the rules interact as follows:
- Read-only + any change attempt: Error - read-only values cannot be modified
- Numeric + positive constraint: Error if the new value is negative
- Numeric + increase-only: Error if new value is less than existing value
- Numeric + decrease-only: Error if new value is greater than existing value
- String + increase-only: Error if new value is greater than existing value
- String + decrease-only: Error if new value is less than existing value
Merged Properties
When creating a child license, the properties from both the parent and child request are merged as a union. However, if read-only (r) is present in either, it takes precedence and removes directional constraints (i, d, p) since read-only attributes cannot be changed at all.
Understanding Attribute Rules in Practice
When issuing a child license, examine the parent's metadata to understand what constraints apply:
{
"expiryDateUTC": "2027-03-24T00:00:00.000Z!i!6EC70AFFE6054B4DFA36!",
"credits": "1000!d!6EC70AFFE6054B4DFA36!"
}
expiryDateUTChas propertyi(decrease-only) - you can only set a date earlier than the parent'screditshas propertyd(decrease-only) - you can only reduce the credit amount
Modifying Attributes in Child Licenses
When creating a child license, you specify metadata changes in the request body. Each attribute value follows the format value:properties::
{
"metaData": {
"platform": "my-platform:r:",
"expiryDateUTC": "2026-06-01T00:00:00.000Z!i!",
"credits": "500!d!"
}
}
- Omit the
changeByfield in your request - the system automatically populates it - The delimiter is automatically selected to avoid conflicts with your value
- If you don't specify properties, the attribute inherits constraints from the parent
For new attributes (not inherited from parent), include your desired constraints. For existing attributes, the merge rules determine the final constraint set.
Issuing Child Licenses
Child licenses are issued through the licensing service at https://licensing.opscotch.co.
Request Preconditions
The request must satisfy these preconditions:
- Request body:
parentLicenseId,ownerKeyHex, andlicenseToare required. - Request headers:
idempotencyKeyis required. - Conditional request body:
metaData.creditsis required only when the parent license is constrained by credits. - Retry rule:
if the request outcome is uncertain, retry the same request with the same
idempotencyKey.
idempotencyKey must be unique to the transaction.
Request Example
IK="$(date +%s)"
curl https://licensing.opscotch.co/licensing/child \
-v \
-H "idempotencyKey:${IK}" \
-H "Content-Type: application/json" \
-X POST \
-d '{
"licenseTo": "<name>",
"parentLicenseId": "<id>",
"ownerKeyHex": "<key>",
"metaData": {
"platform": "<name>:r:",
"expiryDateUTC": "2030-03-13T22:41:27.014Z!i!"
}
}' | jq
Response Example
An issued license response looks like this:
{
"status": 200,
"replay": false,
"license": "AwJhAR+LC...DLlGGtQK",
"licenseId": "6EC7...FA36",
"licenseTo": "acme ltd non-prod",
"ownerKeyHex": "739D550F...9B4D276",
"type": "ORG",
"metadata": {
"env": "non-prod:r:CF06EC1DA6FAA54E23F7:",
"issuedDateUTC": "2026-03-23T21:05:48.094Z!d!6EC70AFFE6054B4DFA36!",
"expiryDateUTC": "2027-03-24T00:00:00.000Z!i!6EC70AFFE6054B4DFA36!",
"service": "opscotch non-prod!r!CF06EC1DA6FAA54E23F7!",
"rootDomain": "opscotch non-prod!r!CF06EC1DA6FAA54E23F7!",
"realm": "prod!r!CF06EC1DA6FAA54E23F7!",
"issuer": "opscotch issuer non-prod:r:E06721004043FF257667:",
"vendor": "opscotch non-prod:r:EF8B05899445AAA66075:",
"org": "acme ltd non-prod:r:6EC7...A36:"
}
}
Retained Response Values
After issuing a child license, store these response values:
licenselicenseIdmetadata.expiryDateUTCownerKeyHex
for PLATFORM licenses store the license for use in the runtime.
licenseId and ownerKeyHex are required for later license-management operations, including reading authorised descendants, issuing further child licenses, and deactivating licenses.
The licensing service returns ownerKeyHex only on the first successful issuance response. Completed replays with the same idempotencyKey do not return it again, so store it securely when the license is first issued.
Using Licenses
There are two common ways to use issued licenses in opscotch:
- configure a runtime to obtain runtime licensing from a licensing service
- embed a
PLATFORMlicense into a packaged application so the package ships with its own runtime license
Licenses are runtime-dependent. opscotch non-prod licenses are valid only on non-production runtimes, and opscotch prod licenses are valid only on production runtimes.
When a package includes an embedded PLATFORM license, a runtime using that package does not need to obtain a separate license to run it.
When packaging opscotch apps, you can add multiple licenses so the package is licensed for multiple environments.
For packaging details, see Packaging.
Operational Benefits
- licensing can be changed without rebuilding packages
- issuers and vendors can delegate licenses downstream without manual repackaging
- organisations can split and manage their own platform allocations
- runtime licensing can be centralised in the licensing app
- attribute-based metadata allows policy and constraints to flow with the license hierarchy
- packages can still embed licenses when that deployment model is preferable
Licensing App
The opscotch licensing app is one deployment pattern for centralised license management.
This app can be added to any opscotch runtime and configured with PLATFORM licenses. Other runtimes can then be configured to obtain runtime licenses from it.
You can install the licensing app:
- in the same runtime
- in a separate opscotch runtime that hosts the organisation's licensing server
The licensing app can be configured with multiple licenses using licensePools.
License Pools Configuration
License pools are configured in the licensing app's bootstrap data under the data property:
{
"data": {
"licensePools": [
{
"id": "default",
"license": "AwJhAR+LC...DLlGGtQK"
},
{
"id": "production",
"license": "AwJhAR+LC...XYZ123"
}
]
}
}
Each pool entry requires:
id: A unique identifier for the poollicense: A valid issuedPLATFORMlicense string
Default pool behavior: If a pool has id: "default", runtime license requests that don't specify a licensePoolId will automatically use this pool. This simplifies configuration when only one pool is needed.
Multiple pools: Configure multiple pools to support different environments, customers, or license types. When multiple pools exist, runtime requests must specify which licensePoolId to use.
Implementation reference:
Development Runtime
Using the opscotch development runtime does not require a registered license.
When the runtime starts in development mode, it will automatically provision a temporary development license with a 1 hour expiry. This means:
- Workflows or apps running for longer than 1 hour will stop when that license expires.
- Restarting the runtime provisions a fresh 1 hour development license.
- Secret redaction is disabled on the automatically provisioned development license.
- Secrets written to logs will be tagged as
<THIS WILL BE REDACTED>secret</THIS WILL BE REDACTED>.
Users can register on the website for a free development license. When using that license, the metadata forceRedaction=true can be applied to enable full redaction behavior.