License Open API Document
v1.0
License Open API Document
This document is for developers who need to integrate a complete "pay → issue → activate → verify" flow into their own software (client software / promotion-only products). Each API below is described with its call timing, request parameters, response fields, and error codes.
1. Core Concepts
| Concept | Field / Term | Description |
|---|---|---|
| Software API secret | licenseApiSecret |
Automatically generated when the product enables "license activation". Visible in the product edit form in the Developer Center. Your software server uses it to compute the HMAC signature and the platform uses it to verify. Store it only on your server; never embed it in the client. |
| License code | licenseCode |
A unique code generated by the platform. Users activate your software with it. |
| Machine code | machineCode |
A device identifier generated by the client software (unique per computer; hash a stable machine fingerprint, at least 8 characters). |
| Activation token | activationToken |
Issued by the platform during activation / code generation. The software stores it and sends it back on every startup verification. |
| Client order ID | clientOrderId |
The order ID of the in-software payment (generated by the client or your server). The platform deduplicates by "product + order ID" to prevent duplicate charges or duplicate code issuance. |
2. Preparation
- Enable "license activation" when publishing the product and save the generated Software API secret (licenseApiSecret);
- Configure the edition list (e.g. BASIC/PRO/ULTIMATE) and the upgrade strategy (SAME_CODE = upgrade in place / NEW_CODE = issue a new code) in the publish form;
- Keep the secret on your software server; the client only keeps the license code and the activation token.
3. API Overview
Base path: https://www.powersoftware.app/frontApi (replace with your deployment domain for local / test environments).
| API | When to call | Authentication | Signature |
|---|---|---|---|
POST /license/software/generate |
Right after an in-software payment succeeds | None (signature verified) | Required |
POST /license/software/upgrade |
Right after a renewal / edition upgrade payment succeeds | None (signature verified) | Required |
POST /license/activate |
User first activation / switching to a new device | None | Not required |
POST /license/verify |
Every time the software starts | None | Not required |
POST /license/deactivate |
User unbinds the old device (Profile Center) | Login | Not required |
4. Signature Rules (required for generate / upgrade)
Payload: concatenate the following fields in this exact order with newline characters (\n). For missing optional fields use empty values (edition empty string, expiryDays = 0):
productId \n machineCode \n edition \n expiryDays \n clientOrderId \n licenseCode \n timestamp
Signature: HMAC-SHA256(Software API secret licenseApiSecret, payload), Base64URL-encoded, and put into the signature field.
Replay protection: timestamp is a millisecond timestamp. The platform requires it to be within 5 minutes of the server time, otherwise signatureInvalid is returned.
Node.js example:
const crypto = require("crypto");
const payload = [productId, machineCode, edition, expiryDays, clientOrderId, licenseCode, timestamp].join("\n");
const signature = crypto.createHmac("sha256", API_SECRET).update(payload).digest("base64url");
Python example:
import hashlib, hmac, base64
payload = "\n".join([str(productId), machineCode, edition, str(expiryDays), clientOrderId, licenseCode, str(timestamp)])
signature = base64.urlsafe_b64encode(hmac.new(API_SECRET.encode(), payload.encode(), hashlib.sha256).digest()).decode().rstrip("=")
(Base64URL is standard Base64 with + replaced by -, / replaced by _, and trailing = padding removed.)
5. API Details
5.1 Generate a License Code (call after in-software payment)
When to call: right after the user finishes an in-software payment, your server calls this once and stores the returned license code and activation token. The same order should be sent only once; repeated calls are idempotent and will not issue another code.
POST /license/software/generate
Request parameters (JSON body):
| Parameter | Type | Required | Source | Description |
|---|---|---|---|---|
| productId | number | Yes | Hard-coded / server config | Product ID, matching the product published on the platform |
| machineCode | string | Yes | Client-generated | Machine code of the current device, at least 8 characters |
| edition | string | No | Chosen by the client at payment | Edition ID; must be in the product edition list; defaults to the first edition of the product |
| expiryDays | number | No | Chosen by the client at payment | Validity in days, 0 = permanent; range 0-3650 |
| clientOrderId | string | Yes | Client / server-generated | In-software payment order ID, 1-64 characters; idempotency key |
| timestamp | number | Yes | Server-generated | Millisecond timestamp |
| signature | string | Yes | Server-computed | HMAC signature, at least 10 characters |
Response content fields:
| Field | Type | Description |
|---|---|---|
| licenseId | number | License record ID |
| licenseCode | string | The license code (write it into your license data) |
| productId | number | Product ID |
| edition | string | Edition ID |
| machineId | number | Bound machine record ID |
| activatedAt | string | Activation time (ISO8601) |
| expiryTime | string/null | Expiry time (ISO8601), null = permanent |
| maxMachines | number | Maximum number of bindable machines |
| activationToken | string | Activation token; store it for startup verification |
Response example:
{
"code": "SUCCESS",
"requestId": "xxx",
"success": true,
"content": {
"licenseId": 1001,
"licenseCode": "AB12-CD34-EF56-GH78",
"productId": 88,
"edition": "PRO",
"machineId": 2001,
"activatedAt": "2026-08-05T10:00:00.000Z",
"expiryTime": "2027-08-05T10:00:00.000Z",
"maxMachines": 1,
"activationToken": "eyJhbGciOiJIUzI1NiJ9..."
}
}
Error codes for this API: productIdRequired, machineCodeInvalid, orderIdRequired, productNotFound, productNotEnabled, apiSecretMissing, signatureInvalid, orderAlreadyUsed.
5.2 Upgrade Edition / Renew (call after payment)
When to call: after the user completes a renewal or an edition-upgrade payment. The same order should be sent only once.
POST /license/software/upgrade
Request parameters (JSON body):
| Parameter | Type | Required | Source | Description |
|---|---|---|---|---|
| productId | number | Yes | Hard-coded / server config | Product ID |
| licenseCode | string | Yes | Stored in the software | Current license code, at least 10 characters |
| machineCode | string | No | Client-generated | Current machine code; pass it when migrating bindings (NEW_CODE strategy) or binding an extra machine |
| edition | string | Yes | Chosen by the client at payment | Target edition ID |
| expiryDays | number | No | Chosen by the client at payment | Additional validity in days, 0 = permanent; range 0-3650 |
| clientOrderId | string | Yes | Client / server-generated | Order ID of this purchase; idempotency key |
| timestamp | number | Yes | Server-generated | Millisecond timestamp |
| signature | string | Yes | Server-computed | HMAC signature |
Handling strategy (determined by the product configuration):
- SAME_CODE (upgrade in place): the license code stays the same, machine bindings stay valid, and the validity is extended (a permanent license stays permanent). The response adds
upgraded: trueto the fields of 5.1; - NEW_CODE (issue a new code): the old code is revoked, a new code is issued, and machine bindings are migrated. The response additionally returns
oldLicenseId, andlicenseCodeis the new code.
Response content fields: same as 5.1, plus upgraded: true; under NEW_CODE the response also returns oldLicenseId (the old license record ID; the old code has been revoked).
Error codes for this API: productIdRequired, licenseCodeRequired, orderIdRequired, productNotFound, productNotEnabled, apiSecretMissing, signatureInvalid, codeNotFound, revoked, orderAlreadyUsed, editionRequired.
5.3 Activate a License (first activation / new device)
When to call: after the user obtains a license code (self-service issuance, admin issuance, etc.) and enters it on the activation page of the client software; call it on the new device when the user switches machines.
POST /license/activate
Request parameters (JSON body):
| Parameter | Type | Required | Source | Description |
|---|---|---|---|---|
| licenseCode | string | Yes | User input | License code, at least 10 characters |
| machineCode | string | Yes | Client-generated | Machine code of the current device, at least 8 characters |
Response content: same as 5.1 (licenseId, licenseCode, productId, edition, machineId, activatedAt, expiryTime, maxMachines, activationToken).
Notes:
- A license code can be bound to multiple machines, limited by
maxMachines(defaults to the product configuration); - Activating the same machine again reuses the existing binding and re-issues an activation token (idempotent);
- Failed activations are rate-limited per IP + license code; too many failures return
tooManyAttempts.
Error codes for this API: codeNotFound, revoked, expired, machineLimit, tooManyAttempts.
5.4 Verify a License (every software startup)
When to call: every time the software starts (or before unlocking features). The software must store the activationToken returned by 5.1 / 5.2 / 5.3.
POST /license/verify
Request parameters (JSON body):
| Parameter | Type | Required | Source | Description |
|---|---|---|---|---|
| licenseCode | string | Yes | Stored in the software | License code, at least 10 characters |
| machineCode | string | Yes | Client-generated | Machine code of the current device, at least 8 characters |
| activationToken | string | Yes | Issued by the platform, stored by the software | Activation token, at least 20 characters |
Response content fields:
| Field | Type | Description |
|---|---|---|
| valid | boolean | Always true (failures are returned as error codes) |
| licenseId | number | License record ID |
| productId | number | Product ID |
| edition | string | Current edition ID |
| expiryTime | string/null | Expiry time (ISO8601), null = permanent |
Notes:
- Verification results are cached for about 60 seconds; write operations (revoke, unbind, upgrade) invalidate the cache immediately, so there is no delay concern;
- The software should check
expiryTimeand stop unlocking features after expiry.
Error codes for this API: tokenInvalid, machineNotActivated, invalidOrRevoked, expired, tooManyAttempts.
5.5 Unbind a Device (before switching machines, login required)
When to call: the user unbinds the old device in "Profile Center → My Licenses", then activates on the new device via 5.3.
POST /license/deactivate
Request parameters (JSON body):
| Parameter | Type | Required | Source | Description |
|---|---|---|---|---|
| licenseCode | string | Yes | User license list | License code, at least 10 characters |
| machineCode | string | Yes | User license list | The machine code to unbind, at least 8 characters |
Response content: true.
Notes:
- Only the owner of the license (the buyer, or the user who registered the bound machine code) can operate;
- Unbind quota: by default the same license can be unbound only once within 30 days (configurable in the admin console); exceeding it returns
unbindQuotaExceeded.
Error codes for this API: noPermission, machineNotBound, unbindQuotaExceeded.
6. Error Codes
Business errors use a unified response (tip carries a human-readable message):
{
"code": "BUSINESS_WARNING",
"requestId": "xxx",
"success": false,
"errorMessage": "LICENSE_GENERATE_FAILED",
"tip": "Signature verification failed"
}
Parameter validation failures return code: "PARAM_VALIDATE_FAILED" and the specific field message in tip.
| Error code | Meaning |
|---|---|
productNotEnabled |
The product does not have license activation enabled |
apiSecretMissing |
The product has no Software API secret (licenseApiSecret) configured |
signatureInvalid |
Signature verification failed (wrong secret, wrong payload order, or timestamp out of window) |
machineCodeInvalid |
Invalid machine code (shorter than 8 characters) |
orderAlreadyUsed |
This order already generated a license code (idempotency conflict: replayed with a different machine code) |
codeNotFound |
License code not found |
revoked |
License code has been revoked or is unusable |
expired |
License code has expired |
invalidOrRevoked |
License is invalid or revoked during verification |
machineNotActivated |
This machine is not activated |
tokenInvalid |
Invalid activation token |
machineLimit |
The license has reached the maximum number of bound machines |
machineNotBound |
This machine is not bound |
noPermission |
No permission to operate on this license |
unbindQuotaExceeded |
The license can be unbound only once within 30 days; try again later |
tooManyAttempts |
Too many attempts; try again later |
productNotFound |
Product not found |
editionRequired |
Please select the target edition |
7. Typical Flows
- In-software payment loop: payment succeeds →
software/generate(returns the license code + activation token) → the software stores them → callverifyon every startup → unlock features; renewal / upgrade →software/upgrade; - Self-service / admin issuance: a license code is generated in the Developer Center or the admin console → the user gets the code →
activatein the client to bind the device and get the activation token →verifyon startup; - Switching machines: unbind the old device via
deactivatein the Profile Center →activateon the new device →verify; - Upgrade strategy difference: SAME_CODE keeps the same code; NEW_CODE issues a new code (the old code is revoked and machine bindings are migrated automatically).
8. Notes
- Keep the secret server-side only: signing must be done on your software server; if the client gets the secret, the license system is compromised;
- Machine code stability: use a stable machine fingerprint and hash it; if the machine code changes after reinstalling the OS, unbind first, then activate again;
- Idempotency: make
clientOrderIdglobally unique (e.g. timestamp + random), repeated requests will not issue duplicate codes; - Cache:
verifyresults are cached for about 60 seconds and invalidated immediately by write operations; - Rate limiting: failed activations / verifications are rate-limited; the client should show proper error messages and retry with delays;
- Trust the platform response: validity, edition, and machine limits are decided by the platform; do not relax them on the software side.