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

  1. Enable "license activation" when publishing the product and save the generated Software API secret (licenseApiSecret);
  2. 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;
  3. 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
trialExpiryTime string/null Trial expiry snapshot (ISO8601), null = not upgraded from a trial
trialExpiryTime string/null Trial expiry snapshot (ISO8601), null = not upgraded from a trial
trialExpiryTime string/null Trial expiry snapshot (ISO8601), null = not upgraded from a trial
trialExpiryTime string/null Trial expiry snapshot (ISO8601), null = not upgraded from a trial
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",
    "trialExpiryTime": "2026-12-01T10:00:00.000Z",
    "trialExpiryTime": "2026-12-01T10:00:00.000Z",
    "trialExpiryTime": "2026-12-01T10:00:00.000Z",
    "trialExpiryTime": "2026-12-01T10: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: true to 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, and licenseCode is 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
trialExpiryTime string/null Trial expiry snapshot (ISO8601), null = not upgraded from a trial
trialExpiryTime string/null Trial expiry snapshot (ISO8601), null = not upgraded from a trial
trialExpiryTime string/null Trial expiry snapshot (ISO8601), null = not upgraded from a trial
trialExpiryTime string/null Trial expiry snapshot (ISO8601), null = not upgraded from a trial

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 expiryTime and stop unlocking features after expiry.
  • If the license originated from a trial, use trialExpiryTime as a grace window for top-tier features: keep them unlocked until trialExpiryTime.
  • If the license originated from a trial, use trialExpiryTime as a grace window for top-tier features: keep them unlocked until trialExpiryTime.
  • If the license originated from a trial, use trialExpiryTime as a grace window for top-tier features: keep them unlocked until trialExpiryTime.
  • If the license originated from a trial, use trialExpiryTime as a grace window for top-tier features: keep them unlocked until trialExpiryTime.

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

  1. In-software payment loop: payment succeeds → software/generate (returns the license code + activation token) → the software stores them → call verify on every startup → unlock features; renewal / upgrade → software/upgrade;
  2. Self-service / admin issuance: a license code is generated in the Developer Center or the admin console → the user gets the code → activate in the client to bind the device and get the activation token → verify on startup;
  3. Switching machines: unbind the old device via deactivate in the Profile Center → activate on the new device → verify;
  4. 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 clientOrderId globally unique (e.g. timestamp + random), repeated requests will not issue duplicate codes;
  • Cache: verify results 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.

6. Trial claim & purchase page

For "Try first" client software products, the client calls POST /license/trial/claim after the free download.

Request parameters:

Param Type Required Description
productUniqueCode string yes Product unique code (must be a Try-first product)
machineCode string yes Machine code (>=8 chars; use the cross-language SDK algorithm)

Success response is the same as 5.1 (licenseCode / activationToken / expiryTime = claim time + trial days). The response also carries trialExpiryTime (trial expiry snapshot, equal to expiryTime). The response also carries trialExpiryTime (trial expiry snapshot, equal to expiryTime). The response also carries trialExpiryTime (trial expiry snapshot, equal to expiryTime). The response also carries trialExpiryTime (trial expiry snapshot, equal to expiryTime). The same product + machine returns the same trial license (idempotent).

After the trial, paid features require purchasing a license edition: when verification fails, prompt the user and open the purchase page with the machine code:

https://www.powersoftware.app/product/license/purchase?productUniqueCode={productUniqueCode}&machineCode={machineCode}

After payment, the platform issues a license (source=PLATFORM), shows it on the purchase page and emails it. SDK reference: ps-help/v3/doc/授权SDK规范_v3.md.

7. Official SDKs (Node / Python / Java)

Official SDKs in three languages (cross-language machine code, HMAC signing, 60s verification cache, purchase-page redirect) plus bilingual specs:

https://github.com/mizhanchengxi/powersoftware-license-sdk

8. Upgrade policy flag (licenseUpgradeMode)

The success response content of POST /license/activate, POST /license/verify and POST /license/trial/claim also returns the product-level upgrade policy:

Field Type Description
licenseUpgradeMode string SAME_CODE = the license code stays the same after upgrade/renewal; NEW_CODE = a new code is issued (old code revoked) on upgrade/renewal

Client usage:

  • Decide whether to show a "bind license code" input: with SAME_CODE the code never changes, so never prompt users to re-enter it; with NEW_CODE a new code is issued on upgrade/renewal — overwrite the locally stored code with the licenseCode returned by the API;
  • verify returns the latest value on every launch, so clients stay in sync.

Notes:

  • Product-level configuration set by the developer in the publish form; defaults to SAME_CODE when unset;
  • verify results are cached for about 60s, so a platform-side config change takes effect within 60s;
  • software/upgrade already returns the actual (old or new) code per policy and does not need this field.

9. Trial expiry snapshot (trialExpiryTime)

The success response content of POST /license/activate, POST /license/verify and POST /license/trial/claim also returns the trial expiry snapshot:

Field Type Description
trialExpiryTime string | null Expiry time of the original trial license (ISO 8601); null = not upgraded from a trial (pure first purchase, developer-issued code, or trial not converted)

How it is produced:

  • Written when the trial is claimed, same value as the trial license's expiryTime;
  • When purchasing after a trial (same-code upgrade or new-code reissue), the platform first reads the trial's original expiry time and freezes it into this field; after purchase expiryTime becomes the purchased edition's validity while this field stays unchanged;
  • Converting an expired trial also freezes the value (a past timestamp), so clients can detect "no grace left";
  • Later paid upgrades/renewals (same code or new code) preserve this field.

Client usage (recommended): trial-first products unlock all features during the trial; after buying a lower edition, higher-tier features are locked immediately by edition gating. Clients may read trialExpiryTime and decide their own transition policy, e.g. keep granting a higher-tier feature while now < trialExpiryTime, then lock it and guide the user to pay the difference after that moment. Whether and how to use it is entirely up to the client; the platform does not enforce anything.

Note: when claiming a trial, if the machine already holds a non-trial license for the product (trial-to-purchase conversion, first purchase, developer-issued code, etc., i.e. already purchased), the API returns error code trialAlreadyPurchased and no additional trial license is issued; revoked (refunded) licenses do not count as purchased.