# Lizenzschlüssel-API

> Lizenzen für eigene Software – Schlüssel aktivieren, prüfen und abmelden, Token offline verifizieren.

Für **Anwendungen der Art „Nur Lizenz“** (Konsole → Anwendungen verwalten). Du stellst Lizenzschlüssel aus, deine
Software aktiviert sie auf einem Gerät und bekommt ein signiertes Token, das sie auch offline prüfen kann.

Basis-URL je Anwendung: `https://updates.cyrellian.com/<anwendung>/license/v1` – steht mit dem öffentlichen
Schlüssel auf der Übersichtsseite der Anwendung in der Konsole.

| Aufruf | Body | Antwort |
|---|---|---|
| `POST activate` | `{key, machineId, name?, version?}` | `{status: "active", token, license}` |
| `POST validate` | `{key` oder `token, machineId, version?}` | `{status: "active", token, license}` |
| `POST deactivate` | `{key` oder `token, machineId}` | `{ok: true}` |
| `GET public-key` | – | `{algorithm: "Ed25519", keys: [{publicKey, active}]}` |

- **`machineId`**: stabile Kennung des Geräts bzw. der Installation, 4–200 Zeichen aus Buchstaben, Ziffern und
  `. _ : -` (z. B. ein Hash aus Rechner-ID und Benutzer).
- **`activate`** belegt einen Geräteplatz. Dasselbe Gerät erneut zu aktivieren zählt nicht doppelt.
- **`validate`** ist das Lebenszeichen: einmal beim Start und etwa täglich. Es liefert ein frisches Token und merkt
  sofort, wenn ein Schlüssel gesperrt, widerrufen oder abgelaufen ist.
- **`deactivate`** gibt den Platz wieder frei (Deinstallation, Umzug auf einen neuen Rechner). Kunden können Geräte
  auch selbst unter account.cyrellian.com abmelden.

`license` enthält `{plan, planName, features, expiresAt, tokenExpiresAt, maxActivations, manageUrl?}`.

## Verwaltungscode

Pro Anwendung einschaltbar (Konsole → Anwendung bearbeiten → „Verwaltungscode zu jedem Schlüssel“). Dann bekommt
jeder neue Schlüssel zusätzlich einen **Verwaltungscode** (`VC-XXXXX-XXXXX-XXXXX-XXXXX`, nur einmal angezeigt). Der
Käufer kann den Lizenzschlüssel weitergeben und behält den Code: Unter `https://account.cyrellian.com/manage` sieht
er damit – **ohne Konto** – Status, Ablauf und alle aktivierten Geräte und meldet Geräte ab. Die API liefert diese
Adresse als `license.manageUrl`, damit deine Software „Lizenz verwalten“ verlinken kann. Verlorene Codes erzeugst du
in der Konsole neu (der alte wird ungültig); ältere Schlüssel bekommen dort ihren ersten Code.

## Fehler

HTTP 4xx mit `{error: <code>}`:

| Code | Bedeutung |
|---|---|
| `invalid_request` | Pflichtfeld fehlt oder `machineId`/Schlüssel hat das falsche Format |
| `invalid_key` (404) | Schlüssel unbekannt |
| `expired` / `suspended` / `revoked` (403) | Lizenz abgelaufen, gesperrt oder widerrufen |
| `limit_reached` (403) | Alle Geräteplätze belegt – erst ein Gerät abmelden |
| `not_activated` (403) | Dieses Gerät ist für den Schlüssel nicht (mehr) aktiviert |
| `rate_limited` (429) | Zu viele Anfragen (bzw. zu viele unbekannte Schlüssel) von dieser IP |
| `not_configured` (503) | Die Anwendung hat keinen Signaturschlüssel |

## Token

`base64url(JSON) + "." + base64url(Ed25519-Signatur über den base64url-Teil)`, signiert mit dem Schlüssel der
Anwendung:

```json
{
  "v": 1,
  "typ": "license_key",
  "iss": "https://updates.cyrellian.com/mein-tool/license/v1",
  "app": "mein-tool",
  "sub": "<machineId>",
  "kid": "<Schlüssel-ID>",
  "plan": "pro",
  "planName": "Pro",
  "features": { "pro": true, "maxProjects": null },
  "status": "active",
  "licenseExpiresAt": "2027-10-03T23:59:59.000Z",
  "iat": 1790000000,
  "exp": 1790604800
}
```

Prüfe offline: Signatur mit dem öffentlichen Schlüssel (Base64, SPKI DER), `sub` = eigene `machineId`, `exp` in
der Zukunft. Ein Token gilt **7 Tage** (höchstens bis zum Ende der Lizenz) – so funktioniert deine Software ohne
Internet weiter, und eine Sperre wirkt spätestens nach dieser Zeit.

## Mit dem Client-Paket

`@cyrellian/license-client` (keine Abhängigkeiten; Node 20+, Electron, Bun, Deno, Browser). Das Paket ist privat und
liegt im Cyrellian-Repository (`packages/license-client`) – die eine Datei `src/index.ts` übernimmst du in deine Software:

```ts
import { LicenseClient } from "@cyrellian/license-client";

const license = new LicenseClient({
  baseUrl: "https://updates.cyrellian.com/mein-tool/license/v1",
  publicKeys: ["MCowBQYDK2VwAyEA…"],
  machineId,
  storage: { get: () => readToken(), set: (t) => writeToken(t) },
  version: "1.4.0",
});

await license.activate("ABCDE-FGHJK-LMNPQ-RSTUV-WXYZ2", { name: "Laptop" });
const state = await license.check();        // online prüfen, offline das gespeicherte Token
if (state.valid && state.features.pro) {
  // Pro-Funktionen freischalten
}
await license.deactivate();
```

## Direkt per HTTP

```bash
curl -X POST https://updates.cyrellian.com/mein-tool/license/v1/activate \
  -H "content-type: application/json" \
  -d '{"key":"ABCDE-FGHJK-LMNPQ-RSTUV-WXYZ2","machineId":"laptop-7f3a9c","name":"Laptop"}'
```
