Documentation API ouverte des licences
v1.0
Documentation API ouverte des licences
Ce document s'adresse aux développeurs qui souhaitent intégrer le flux complet « paiement → émission → activation → vérification » dans leur propre logiciel (logiciel client / produits en promotion seule). Chaque API est décrite avec son moment d'appel, ses paramètres de requête, ses champs de réponse et ses codes d'erreur.
1. Concepts de base
| Concept | Champ / terme | Description |
|---|---|---|
| Secret API logiciel | licenseApiSecret |
Généré automatiquement lorsque le produit active « activation de licence ». Consultable dans le formulaire d'édition du produit du Centre développeur. Votre serveur logiciel l'utilise pour calculer la signature HMAC, la plateforme l'utilise pour la vérifier. À conserver uniquement côté serveur, ne jamais l'intégrer au client. |
| Code de licence | licenseCode |
Code unique généré par la plateforme. L'utilisateur l'utilise pour activer votre logiciel. |
| Code machine | machineCode |
Identifiant d'appareil généré par le logiciel client (unique par ordinateur ; hacher une empreinte machine stable, 8 caractères minimum). |
| Jeton d'activation | activationToken |
Délivré par la plateforme lors de l'activation / de l'émission. Le logiciel le conserve et le renvoie à chaque vérification au démarrage. |
| Identifiant de commande | clientOrderId |
Identifiant de la commande de paiement intégré (généré par le client ou le serveur). La plateforme déduplique via « produit + identifiant de commande » pour éviter les doubles débits et double émission. |
2. Préparation
- Lors de la publication, activez l'« activation de licence » et conservez le Secret API logiciel (licenseApiSecret) généré ;
- Dans le formulaire de publication, configurez la liste des éditions (ex. BASIC/PRO/ULTIMATE) et la stratégie de mise à niveau (SAME_CODE = mise à niveau avec le même code / NEW_CODE = émettre un nouveau code) ;
- Conservez le secret sur votre serveur logiciel ; le client ne garde que le code de licence et le jeton d'activation.
3. Vue d'ensemble des API
Préfixe : https://www.powersoftware.app/frontApi (remplacez par votre domaine de déploiement en local / test).
| API | Moment d'appel | Authentification | Signature |
|---|---|---|---|
POST /license/software/generate |
Juste après un paiement intégré réussi | Aucune (vérification de signature) | Requise |
POST /license/software/upgrade |
Juste après un paiement de renouvellement / mise à niveau réussi | Aucune (vérification de signature) | Requise |
POST /license/activate |
Première activation / changement d'appareil | Aucune | Non requise |
POST /license/verify |
À chaque démarrage du logiciel | Aucune | Non requise |
POST /license/deactivate |
L'utilisateur dissocie l'ancien appareil (Mon profil) | Connexion | Non requise |
4. Règles de signature (requises pour generate / upgrade)
Chaîne à signer : concaténez les champs suivants dans cet ordre exact avec des retours à la ligne (\n) (champs facultatifs absents avec des valeurs vides : edition chaîne vide, expiryDays = 0) :
productId \n machineCode \n edition \n expiryDays \n clientOrderId \n licenseCode \n timestamp
Signature : HMAC-SHA256(Secret API logiciel licenseApiSecret, chaîne à signer), encodée en Base64URL, à placer dans le champ signature.
Protection contre la relecture : timestamp est un horodatage en millisecondes. La plateforme exige un écart maximal de 5 minutes avec l'heure du serveur, sinon signatureInvalid est renvoyé.
Exemple Node.js :
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");
Exemple Python :
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("=")
(Le Base64URL est le Base64 standard avec + remplacé par -, / remplacé par _, et le remplissage = final supprimé.)
5. Détail des API
5.1 Générer un code de licence (appeler après paiement intégré)
Moment d'appel : juste après qu'un paiement intégré a réussi, votre serveur appelle l'API une fois et conserve le code de licence et le jeton d'activation retournés. La même commande ne doit être envoyée qu'une seule fois ; les appels répétés sont idempotents et n'émettent pas de second code.
POST /license/software/generate
Paramètres de requête (corps JSON) :
| Paramètre | Type | Obligatoire | Source | Description |
|---|---|---|---|---|
| productId | number | Oui | Intégré / config serveur | Identifiant du produit, identique au produit publié sur la plateforme |
| machineCode | string | Oui | Généré par le client | Code machine de l'appareil actuel, 8 caractères minimum |
| edition | string | Non | Choisi par le client au paiement | Identifiant d'édition ; doit être dans la liste des éditions ; par défaut la première édition du produit |
| expiryDays | number | Non | Choisi par le client au paiement | Durée de validité en jours, 0 = permanent ; plage 0-3650 |
| clientOrderId | string | Oui | Généré par le client / serveur | Identifiant de commande du paiement intégré, 1-64 caractères ; clé d'idempotence |
| timestamp | number | Oui | Généré par le serveur | Horodatage en millisecondes |
| signature | string | Oui | Calculé par le serveur | Signature HMAC, 10 caractères minimum |
Champs de réponse de content :
| Champ | Type | Description |
|---|---|---|
| licenseId | number | Identifiant de l'enregistrement de licence |
| licenseCode | string | Le code de licence (à écrire dans vos données de licence) |
| productId | number | Identifiant du produit |
| edition | string | Identifiant d'édition |
| machineId | number | Identifiant de l'enregistrement de machine liée |
| activatedAt | string | Heure d'activation (ISO8601) |
| expiryTime | string/null | Heure d'expiration (ISO8601), null = permanent |
| trialExpiryTime | string/null | Instantané de l’expiration d’essai (ISO8601), null = pas d’origine essai |
| trialExpiryTime | string/null | Instantané de l’expiration d’essai (ISO8601), null = pas d’origine essai |
| trialExpiryTime | string/null | Instantané de l’expiration d’essai (ISO8601), null = pas d’origine essai |
| trialExpiryTime | string/null | Instantané de l’expiration d’essai (ISO8601), null = pas d’origine essai |
| maxMachines | number | Nombre maximal de machines liables |
| activationToken | string | Jeton d'activation ; à conserver pour la vérification au démarrage |
Exemple de réponse :
{
"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..."
}
}
Codes d'erreur de cette API : productIdRequired, machineCodeInvalid, orderIdRequired, productNotFound, productNotEnabled, apiSecretMissing, signatureInvalid, orderAlreadyUsed.
5.2 Mettre à niveau / renouveler (appeler après paiement)
Moment d'appel : après qu'un paiement de renouvellement ou de mise à niveau a réussi. La même commande ne doit être envoyée qu'une seule fois.
POST /license/software/upgrade
Paramètres de requête (corps JSON) :
| Paramètre | Type | Obligatoire | Source | Description |
|---|---|---|---|---|
| productId | number | Oui | Intégré / config serveur | Identifiant du produit |
| licenseCode | string | Oui | Stocké dans le logiciel | Code de licence actuel, 10 caractères minimum |
| machineCode | string | Non | Généré par le client | Code machine actuel ; à transmettre pour la migration des liaisons (NEW_CODE) ou une liaison supplémentaire |
| edition | string | Oui | Choisi par le client au paiement | Identifiant d'édition cible |
| expiryDays | number | Non | Choisi par le client au paiement | Durée de validité supplémentaire en jours, 0 = permanent ; plage 0-3650 |
| clientOrderId | string | Oui | Généré par le client / serveur | Identifiant de commande de cet achat ; clé d'idempotence |
| timestamp | number | Oui | Généré par le serveur | Horodatage en millisecondes |
| signature | string | Oui | Calculé par le serveur | Signature HMAC |
Stratégie de traitement (déterminée par la configuration du produit) :
- SAME_CODE (mise à niveau avec le même code) : le code de licence ne change pas, les liaisons machine restent valides et la validité est prolongée (une licence permanente reste permanente). La réponse ajoute
upgraded: trueaux champs du 5.1 ; - NEW_CODE (émettre un nouveau code) : l'ancien code est révoqué, un nouveau code est émis et les liaisons machine sont migrées. La réponse renvoie en plus
oldLicenseId, etlicenseCodeest le nouveau code.
Champs de réponse de content : identiques au 5.1, plus upgraded: true ; en NEW_CODE, oldLicenseId est aussi renvoyé (identifiant de l'ancien enregistrement ; l'ancien code a été révoqué).
Codes d'erreur de cette API : productIdRequired, licenseCodeRequired, orderIdRequired, productNotFound, productNotEnabled, apiSecretMissing, signatureInvalid, codeNotFound, revoked, orderAlreadyUsed, editionRequired.
5.3 Activer une licence (première activation / nouvel appareil)
Moment d'appel : après que l'utilisateur a obtenu un code de licence (émission libre, émission par l'administration, etc.) et le saisit sur la page d'activation du logiciel client ; appeler sur le nouvel appareil lors d'un changement d'appareil.
POST /license/activate
Paramètres de requête (corps JSON) :
| Paramètre | Type | Obligatoire | Source | Description |
|---|---|---|---|---|
| licenseCode | string | Oui | Saisie utilisateur | Code de licence, 10 caractères minimum |
| machineCode | string | Oui | Généré par le client | Code machine de l'appareil actuel, 8 caractères minimum |
Champs de réponse de content : identiques au 5.1 (licenseId, licenseCode, productId, edition, machineId, activatedAt, expiryTime, maxMachines, activationToken).
Remarques :
- Un code de licence peut être lié à plusieurs machines, dans la limite de
maxMachines(par défaut la configuration du produit) ; - Activer à nouveau la même machine réutilise la liaison existante et réémet un jeton d'activation (idempotent) ;
- Les activations en échec sont limitées par IP + code de licence ; trop d'échecs renvoient
tooManyAttempts.
Codes d'erreur de cette API : codeNotFound, revoked, expired, machineLimit, tooManyAttempts.
5.4 Vérifier une licence (à chaque démarrage)
Moment d'appel : à chaque démarrage du logiciel (ou avant de déverrouiller des fonctions). Le logiciel doit conserver le jeton activationToken renvoyé par 5.1 / 5.2 / 5.3.
POST /license/verify
Paramètres de requête (corps JSON) :
| Paramètre | Type | Obligatoire | Source | Description |
|---|---|---|---|---|
| licenseCode | string | Oui | Stocké dans le logiciel | Code de licence, 10 caractères minimum |
| machineCode | string | Oui | Généré par le client | Code machine de l'appareil actuel, 8 caractères minimum |
| activationToken | string | Oui | Émis par la plateforme, stocké par le logiciel | Jeton d'activation, 20 caractères minimum |
Champs de réponse de content :
| Champ | Type | Description |
|---|---|---|
| valid | boolean | Toujours true (les échecs passent par les codes d'erreur) |
| licenseId | number | Identifiant de l'enregistrement de licence |
| productId | number | Identifiant du produit |
| edition | string | Identifiant d'édition actuel |
| expiryTime | string/null | Heure d'expiration (ISO8601), null = permanent |
| trialExpiryTime | string/null | Instantané de l’expiration d’essai (ISO8601), null = pas d’origine essai |
| trialExpiryTime | string/null | Instantané de l’expiration d’essai (ISO8601), null = pas d’origine essai |
| trialExpiryTime | string/null | Instantané de l’expiration d’essai (ISO8601), null = pas d’origine essai |
| trialExpiryTime | string/null | Instantané de l’expiration d’essai (ISO8601), null = pas d’origine essai |
Remarques :
- Les résultats de vérification sont mis en cache environ 60 secondes ; les opérations d'écriture (révocation, dissociation, mise à niveau) invalident immédiatement le cache, aucun délai à craindre ;
- Le logiciel doit vérifier
expiryTimeet cesser de déverrouiller les fonctions après expiration. - Si la licence provient d’un essai, utilisez
trialExpiryTimecomme période de grâce : les fonctions restent déverrouillées jusqu’àtrialExpiryTime. - Si la licence provient d’un essai, utilisez
trialExpiryTimecomme période de grâce : les fonctions restent déverrouillées jusqu’àtrialExpiryTime. - Si la licence provient d’un essai, utilisez
trialExpiryTimecomme période de grâce : les fonctions restent déverrouillées jusqu’àtrialExpiryTime. - Si la licence provient d’un essai, utilisez
trialExpiryTimecomme période de grâce : les fonctions restent déverrouillées jusqu’àtrialExpiryTime.
Codes d'erreur de cette API : tokenInvalid, machineNotActivated, invalidOrRevoked, expired, tooManyAttempts.
5.5 Dissocier un appareil (avant changement d'appareil, connexion requise)
Moment d'appel : l'utilisateur dissocie l'ancien appareil dans « Mon profil → Mes licences », puis active sur le nouvel appareil via 5.3.
POST /license/deactivate
Paramètres de requête (corps JSON) :
| Paramètre | Type | Obligatoire | Source | Description |
|---|---|---|---|---|
| licenseCode | string | Oui | Liste des licences de l'utilisateur | Code de licence, 10 caractères minimum |
| machineCode | string | Oui | Liste des licences de l'utilisateur | Le code machine à dissocier, 8 caractères minimum |
Champs de réponse de content : true.
Remarques :
- Seul le propriétaire de la licence (l'acheteur ou l'utilisateur ayant enregistré le code machine lié) peut effectuer cette opération ;
- Quota de dissociation : par défaut, la même licence ne peut être dissociée qu'une fois en 30 jours (configurable dans la console d'administration) ; au-delà,
unbindQuotaExceededest renvoyé.
Codes d'erreur de cette API : noPermission, machineNotBound, unbindQuotaExceeded.
6. Codes d'erreur
Les erreurs métier utilisent une réponse unifiée (tip contient un message lisible) :
{
"code": "BUSINESS_WARNING",
"requestId": "xxx",
"success": false,
"errorMessage": "LICENSE_GENERATE_FAILED",
"tip": "Signature verification failed"
}
Les échecs de validation des paramètres renvoient code: "PARAM_VALIDATE_FAILED" et le message du champ concerné dans tip.
| Code d'erreur | Signification |
|---|---|
productNotEnabled |
Le produit n'a pas activé l'activation de licence |
apiSecretMissing |
Le produit n'a aucun Secret API logiciel (licenseApiSecret) configuré |
signatureInvalid |
Échec de vérification de la signature (secret incorrect, ordre de la chaîne incorrect ou horodatage hors fenêtre) |
machineCodeInvalid |
Code machine invalide (moins de 8 caractères) |
orderAlreadyUsed |
Cette commande a déjà généré un code de licence (conflit d'idempotence : rejouée avec un autre code machine) |
codeNotFound |
Code de licence introuvable |
revoked |
Code de licence révoqué ou inutilisable |
expired |
Code de licence expiré |
invalidOrRevoked |
Licence invalide ou révoquée lors de la vérification |
machineNotActivated |
Cette machine n'est pas activée |
tokenInvalid |
Jeton d'activation invalide |
machineLimit |
Le nombre maximal de machines liées est atteint |
machineNotBound |
Cette machine n'est pas liée |
noPermission |
Aucune permission sur cette licence |
unbindQuotaExceeded |
La licence ne peut être dissociée qu'une fois en 30 jours ; réessayez plus tard |
tooManyAttempts |
Trop de tentatives ; réessayez plus tard |
productNotFound |
Produit introuvable |
editionRequired |
Veuillez choisir l'édition cible |
7. Flux typiques
- Boucle de paiement intégré : paiement réussi →
software/generate(renvoie le code de licence + le jeton d'activation) → le logiciel les conserve →verifyà chaque démarrage → déverrouiller les fonctions ; renouvellement / mise à niveau →software/upgrade; - Émission libre / par l'administration : un code de licence est généré dans le Centre développeur ou la console d'administration → l'utilisateur reçoit le code →
activatedans le client pour lier l'appareil et obtenir le jeton →verifyau démarrage ; - Changement d'appareil : dissocier l'ancien appareil via
deactivatedans Mon profil →activatesur le nouvel appareil →verify; - Différence de stratégie de mise à niveau : SAME_CODE conserve le même code ; NEW_CODE émet un nouveau code (l'ancien est révoqué, les liaisons machine sont migrées automatiquement).
8. Remarques
- Secret côté serveur uniquement : la signature ne doit être calculée que sur votre serveur logiciel ; si le client obtient le secret, le système de licence est compromis ;
- Stabilité du code machine : utilisez une empreinte machine stable et hachez-la ; si le code machine change après réinstallation du système, dissociez d'abord, puis réactivez ;
- Idempotence : rendez
clientOrderIdglobalement unique (ex. horodatage + aléa) ; les demandes répétées n'émettent pas de doublons ; - Cache : les résultats de
verifysont mis en cache environ 60 secondes et invalidés immédiatement par les opérations d'écriture ; - Limitation de débit : les activations / vérifications en échec sont limitées ; le client doit afficher des messages d'erreur appropriés et espacer les tentatives ;
- Fiez-vous à la réponse de la plateforme : validité, édition et limites de machines sont déterminées par la plateforme ; ne les assouplissez pas côté logiciel.
6. Obtention de l’essai & page d’achat
Pour les produits "Try first", le client appelle POST /license/trial/claim après le téléchargement gratuit (productUniqueCode / machineCode, idempotent par produit+machine).
Après l’essai, les fonctions payantes nécessitent l’achat d’une édition sous licence. Page d’achat :
https://www.powersoftware.app/product/license/purchase?productUniqueCode={productUniqueCode}&machineCode={machineCode}.
Après le paiement, une licence est émise et envoyée par e-mail. SDK : ps-help/v3/doc/授权SDK规范_v3.md。
7. SDK officiels (Node / Python / Java)
SDK officiels dans trois langages et spécifications (FR/EN) :
https://github.com/mizhanchengxi/powersoftware-license-sdk
7. SDK officiels (Node / Python / Java)
SDK officiels dans trois langages et spécifications (FR/EN) :
https://github.com/mizhanchengxi/powersoftware-license-sdk
8. Indicateur de politique de mise à niveau (licenseUpgradeMode)
La réponse de succès (content) de POST /license/activate, POST /license/verify et POST /license/trial/claim renvoie également la politique de mise à niveau du produit :
| Champ | Type | Description |
|---|---|---|
| licenseUpgradeMode | string | SAME_CODE = la clé de licence reste inchangée après mise à niveau / renouvellement ; NEW_CODE = une nouvelle clé est émise (l’ancienne est révoquée) |
Côté client :
- Détermine s’il faut afficher le champ de saisie « Lier une clé de licence » : avec
SAME_CODE, la clé ne change jamais, inutile de demander une nouvelle saisie ; avecNEW_CODE, une nouvelle clé est émise — remplacez la clé stockée localement par lelicenseCoderenvoyé ; verifyrenvoie la configuration la plus récente à chaque démarrage, le client reste donc synchronisé.
Remarques :
- Configuration au niveau du produit (définie par le développeur dans le formulaire de publication) ; par défaut
SAME_CODEsi absente ; - Les résultats de
verifysont mis en cache ~60 s : un changement de configuration prend effet sous 60 s au plus ; software/upgraderenvoie déjà la clé réelle (ancienne ou nouvelle) selon la politique ; ce champ n’y est pas nécessaire.
9. Instantané d'expiration d'essai (trialExpiryTime)
La réponse de succès (content) de POST /license/activate, POST /license/verify et POST /license/trial/claim renvoie également l'instantané d'expiration d'essai :
| Champ | Type | Description |
|---|---|---|
| trialExpiryTime | string | null | Date d'expiration de la licence d'essai d'origine (ISO 8601) ; null = pas de conversion depuis un essai (premier achat pur, code émis par le développeur ou essai non converti) |
Règles de production :
- Écrit lors de la demande d'essai, même valeur que l'
expiryTimede la licence d'essai ; - Lors d'un achat après essai (mise à niveau même code ou réémission), la plateforme lit d'abord la date d'expiration d'origine du code d'essai et la fige dans ce champ ; après achat,
expiryTimedevient la validité de l'édition achetée, ce champ reste inchangé ; - La conversion d'un essai expiré fige aussi la valeur (horodaté passé), le client peut détecter « plus de grâce » ;
- Les mises à niveau / renouvellements payants ultérieurs (même code ou nouveau code) conservent ce champ.
Côté client (recommandé) : les produits essai-avant-achat déverrouillent tout pendant l'essai ; après l'achat d'une édition inférieure, les fonctions supérieures sont immédiatement verrouillées par la porte d'édition. Le client peut lire trialExpiryTime et décider sa propre stratégie de transition, par ex. maintenir une fonction supérieure tant que maintenant < trialExpiryTime, puis la verrouiller et guider vers le paiement de la différence. L'utilisation et le mode de grâce relèvent du client ; la plateforme n'impose rien.
Remarque : lors de la demande d'essai, si la machine détient déjà une licence non-essai pour le produit (conversion essai-achat, premier achat, code émis par le développeur, etc., c'est-à-dire déjà acheté), l'API renvoie le code d'erreur trialAlreadyPurchased et n'émet pas de licence d'essai supplémentaire ; les licences révoquées (remboursées) ne comptent pas comme achetées.