> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pulse-sell.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Protéger votre logiciel avec des licences

> Valider la clé de licence d'un client depuis votre serveur : validation, activation par appareil, cache et pannes réseau.

Vous vendez un logiciel, une application ou un accès sur Pulse-Sell. Chaque achat émet une clé de licence. Voici comment votre produit vérifie cette clé de manière sûre.

## Le principe

```mermaid theme={null}
sequenceDiagram
  participant U as Utilisateur
  participant A as Votre application
  participant B as Votre serveur
  participant P as API Pulse-Sell
  U->>A: Saisit sa clé
  A->>B: Envoie la clé et l'identifiant de l'appareil
  B->>P: POST /v1/licenses/validate
  P-->>B: valid, reason, activations
  B->>P: POST /v1/licenses/{clé}/activate (si nouvel appareil)
  B-->>A: Accès accordé ou refusé
```

> [!WARNING]
>
> La validation se fait **toujours depuis votre serveur**, jamais depuis l'application du client. Votre clé d'API (`psk_…`) ne doit jamais être dans un code distribué : quelqu'un l'extrairait en quelques minutes. Créez une clé dédiée avec les permissions `licenses.read` (validation) et `licenses.write` (activation) seulement.

## Étape 1 : valider

```javascript theme={null}
// serveur.js — Node.js (Express)
import express from "express";

const API = "https://api.pulse-sell.com/v1";
const headers = {
  Authorization: `Bearer ${process.env.PULSESELL_API_KEY}`,
  "Content-Type": "application/json",
};

async function validate(key, deviceId) {
  const res = await fetch(`${API}/licenses/validate`, {
    method: "POST", headers, body: JSON.stringify({ key, device_id: deviceId }),
    signal: AbortSignal.timeout(8000), // ne jamais attendre indéfiniment
  });
  if (!res.ok) throw new Error(`Pulse-Sell ${res.status}`);
  return (await res.json()).data;
}

const app = express();
app.use(express.json());

app.post("/api/license/check", async (req, res) => {
  const { key, deviceId } = req.body;
  try {
    const lic = await validate(key, deviceId);
    if (!lic.valid) return res.status(403).json({ ok: false, reason: lic.reason });
    res.json({ ok: true, expiresAt: lic.expires_at, remaining: lic.activations.remaining,
               activated: lic.device_activated === true });
  } catch {
    res.status(503).json({ ok: false, reason: "unavailable" });
  }
});
```

## Étape 2 : activer un nouvel appareil

Si `device_activated` est faux et `activations.remaining` est supérieur à 0, activez :

```javascript theme={null}
async function activate(key, deviceId, deviceName) {
  const res = await fetch(`${API}/licenses/${encodeURIComponent(key)}/activate`, {
    method: "POST", headers, body: JSON.stringify({ device_id: deviceId, device_name: deviceName }),
  });
  if (res.status === 409) return { ok: false, reason: "limit_reached" }; // plus de place
  if (!res.ok) throw new Error(`Pulse-Sell ${res.status}`);
  return { ok: true };
}
```

Le même appareil peut être activé plusieurs fois sans conséquence. Quand le quota est atteint, proposez à l'utilisateur de libérer un ancien appareil avec `POST /licenses/{clé}/deactivate`.

## Étape 3 : gérer les cas limites

| `reason` | Que montrer à l'utilisateur |
| - | - |
| `not_found` | « Clé invalide. Vérifiez votre saisie. » |
| `expired` | « Votre licence a expiré. » avec un lien de renouvellement. |
| `revoked` | « Cette licence n'est plus valide. » (restez vague : ne donnez pas d'indices à un fraudeur). |
| `inactive` | « Cette licence est désactivée. Contactez le support. » |

## Cache et pannes réseau

Ne validez pas à chaque lancement si vous pouvez l'éviter :

* **Mettez en cache** le résultat valide côté serveur (par exemple 1 heure) pour réduire vos appels.
* Pour une application de bureau, **revalidez au moins toutes les 24 heures**, et conservez un résultat de validation signé par **votre** serveur, pas la réponse brute de Pulse-Sell.
* Si Pulse-Sell est injoignable (réseau, panne), accordez un **délai de grâce** de quelques jours à une licence déjà validée. Un paiement ne doit pas bloquer un client honnête parce qu'un réseau est coupé.
* N'affichez pas de message d'erreur détaillé qui aiderait un attaquant.

## Renouvellements et expirations

Abonnez-vous aux [Pulses](https://docs.pulse-sell.com/pulses/events.md) pour automatiser le cycle de vie sans interroger l'API :

* `license.expiring` (7 jours avant) : envoyez un rappel de renouvellement ;
* `license.expired` : coupez les fonctions payantes ou proposez de renouveler ;
* `license.revoked` : invalidez immédiatement vos caches.

> [!TIP]
>
> Créez un Pulse limité aux `product_ids` de votre logiciel : vous ne recevez que les événements qui vous concernent.

## Version Python

```python theme={null}
import os, requests

API = "https://api.pulse-sell.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['PULSESELL_API_KEY']}"}

def validate(key: str, device_id: str) -> dict:
    r = requests.post(f"{API}/licenses/validate", headers=HEADERS,
                      json={"key": key, "device_id": device_id}, timeout=8)
    r.raise_for_status()
    return r.json()["data"]

def activate(key: str, device_id: str, name: str = "") -> bool:
    r = requests.post(f"{API}/licenses/{key}/activate", headers=HEADERS,
                      json={"device_id": device_id, "device_name": name}, timeout=8)
    if r.status_code == 409:
        return False  # quota atteint
    r.raise_for_status()
    return True
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.