Licences
A licence is a one-of-a-kind key issued at purchase for type: license products. Each licence has a maximum activation count (maxActivations); your software calls POST /licenses/:key/activate from the user's device on first launch and burns one activation. Storlaunch's licence storage is canonical in Fulkruma — these endpoints proxy through to Fulkruma so you have a single auth surface.
The activation and validation endpoints are unauthenticated by design: your distributed software can hit them with just the licence key. The list and revoke endpoints are merchant-scoped and need an sk_* key.
Endpoints
| Method | Path | Auth | Purpose |
|---|---|---|---|
GET |
/v1/storefront/licenses |
sk_* |
List all licences for this workspace |
GET |
/v1/storefront/licenses/validate?key=… |
none | Validate a licence key (read-only) |
GET |
/v1/storefront/licenses/:key |
sk_* |
Retrieve one licence by key |
POST |
/v1/storefront/licenses/:key/activate |
none | Activate a licence on an installation |
POST |
/v1/storefront/licenses/:key/deactivate |
none | Free an activation slot |
DELETE |
/v1/storefront/licenses/:key |
sk_* |
Revoke a licence |
Licence storage lives in Fulkruma. Storlaunch keeps no local
Licensetable — reads and writes round-trip to Fulkruma using your scoped account context. There's a single network hop on every operation; cache validate-success responses for a few minutes on your end if you're going to call from a hot loop.
List licences
GET /v1/storefront/licenses
Returns every licence ever issued in the workspace, sorted newest first. No pagination cursor at the moment — the list endpoint returns the full set in one shot.
Response — 200 OK
{
"data": [
{
"id": "lic_01HXAB7K3M9N2P5QRS8TVWXY3Z",
"key": "STORLAUNCH-AB12-CD34-EF56-GH78",
"productId": "prod_01HXxxxxxxxxxxxxxxxxxxxxxx",
"customerId": "cus_01HXxxxxxxxxxxxxxxxxxxxxxx",
"checkoutSessionId": "cs_01HXxxxxxxxxxxxxxxxxxxxxxx",
"activations": 1,
"maxActivations": 3,
"status": "active",
"expiresAt": null,
"createdAt": "2026-05-13T10:42:00Z"
}
],
"error": null,
"meta": { "requestId": "req_01HX...", "timestamp": "2026-05-13T10:42:00Z" }
}
Validate a licence
GET /v1/storefront/licenses/validate?key=…&productId=…
Unauthenticated read. Returns whether the key is valid and (optionally) bound to the right product. Never throws on a bad key — you get valid: false and an empty body.
Use this from your software's startup check loop: it's free, public, and safe to call repeatedly.
Query parameters
| Param | Type | Required | Description |
|---|---|---|---|
key |
string | yes | The licence key. Case-sensitive. |
productId |
string | no | Bind the validation to a specific product. valid is false if the key exists but is for another product. |
Response — 200 OK
{
"data": {
"valid": true,
"key": "STORLAUNCH-AB12-CD34-EF56-GH78",
"productId": "prod_01HX...",
"activations": 1,
"maxActivations": 3,
"expiresAt": null
},
"error": null,
"meta": { "requestId": "req_01HX...", "timestamp": "2026-05-13T10:42:00Z" }
}
When valid is false, the rest of the fields may be null.
Retrieve a licence
GET /v1/storefront/licenses/:key
Merchant-authenticated read by key. Returns the full licence object.
| Status | error.code |
When |
|---|---|---|
404 |
RESOURCE_NOT_FOUND |
Key doesn't exist in this workspace. |
Activate a licence
POST /v1/storefront/licenses/:key/activate
Burns one activation slot. Idempotent on instanceId — calling with the same instanceId twice doesn't double-count, and the response includes alreadyActive: true on the repeat.
Path parameters
| Param | Type | Description |
|---|---|---|
key |
string | The licence key. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
instanceId |
string | yes | A stable, device-or-install-unique ID. UUID, machine ID, anything — just keep it consistent across that install's restarts so re-activation doesn't burn slots. |
Response — 200 OK
{
"data": {
"activated": true,
"key": "STORLAUNCH-AB12-CD34-EF56-GH78",
"activations": 2,
"maxActivations": 3,
"instanceId": "device-7f3a91b2",
"alreadyActive": false
},
"error": null,
"meta": { "requestId": "req_01HX...", "timestamp": "2026-05-13T10:42:00Z" }
}
Errors specific to this endpoint
| Status | error.code |
When |
|---|---|---|
404 |
RESOURCE_NOT_FOUND |
Invalid or revoked licence key. |
422 |
SUBSCRIPTION_INACTIVE |
Key has expired (expiresAt in the past). |
422 |
ACTIVATION_LIMIT_REACHED |
All activation slots are in use. The user needs to deactivate one or buy more seats. |
Deactivate a licence
POST /v1/storefront/licenses/:key/deactivate
Frees one activation slot. Idempotent on instanceId — releasing twice is fine; you get alreadyDeactivated: true.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
instanceId |
string | yes | The same install ID used in activate. |
Response — 200 OK
{
"data": {
"deactivated": true,
"key": "STORLAUNCH-AB12-CD34-EF56-GH78",
"activations": 1,
"instanceId": "device-7f3a91b2",
"alreadyDeactivated": false
},
"error": null,
"meta": { "requestId": "req_01HX...", "timestamp": "2026-05-13T10:42:00Z" }
}
Revoke a licence
DELETE /v1/storefront/licenses/:key
Permanently invalidates the licence. Merchant-only. Use sparingly — revoking is for chargebacks, fraud, and abuse only. For a refund, deactivate and let the licence expire naturally so audit trails stay clean.
Response — 204 No Content.
// Node
await client.licenses.revoke('STORLAUNCH-AB12-CD34-EF56-GH78');
The licence object
| Field | Type | Nullable | Description |
|---|---|---|---|
id |
string | no | Fulkruma's internal ID. Prefixed lic_. |
key |
string | no | The user-visible key. Format depends on the licence template; usually STORLAUNCH-XXXX-XXXX-XXXX-XXXX. Always unique. |
productId |
string | no | The product this licence is for. |
customerId |
string | yes | The buyer (when known). |
checkoutSessionId |
string | yes | The checkout session that issued it. |
activations |
integer | no | Count of currently-active installs. |
maxActivations |
integer | no | Cap (from the product config at issue time). |
status |
enum | no | active, expired, revoked. |
expiresAt |
string (ISO 8601 UTC) | yes | Hard expiry, if set. |
createdAt |
string (ISO 8601 UTC) | no | Issue time. |
Activation pattern
Your software's startup flow typically looks like:
- Read the cached
keyandinstanceIdfrom local storage. - If
instanceIdis empty, generate one and persist it. - Call
GET /validate?key=…. - If
valid: trueandactivations >= 1, proceed. - If
valid: truebutactivations == 0, callPOST /:key/activate. - If
valid: false, fall back to the licence-entry UI.
Cache the validate result locally for 5–15 minutes so you're not hammering the API every cold start. Revalidate on long uptimes (every few hours) to pick up revocations.
Events
Licence lifecycle events fire from Fulkruma directly, not Storlaunch — subscribe to them on your Fulkruma webhook endpoint, not Storlaunch's. The Storlaunch outbox stays catalogue-focused.
You'll see product.purchased (Storlaunch event) at the moment a licence is issued; treat that as the "new licence" trigger if you want a single subscription surface.
Next
- Products — flip
licenseEnabled: trueto issue licences on purchase. - Deliveries — the licence key is included in the delivery record.
- Public storefront — how buyers receive their key.