# Lizenz-API

> Konto-Verknüpfung und Lizenz-Abgleich für selbst gehostete Installationen.

Basis-URL je Produkt: `https://account.cyrellian.com/<produkt>` (z. B. `/plazello`). Alle Aufrufe sind `POST` mit
JSON. Die Verknüpfung funktioniert wie ein „Device Flow“: die Installation fragt einen Code an, der Besitzer bestätigt
ihn im Browser, die Installation holt sich die Lizenz ab.

| Aufruf | Body / Header | Antwort |
|---|---|---|
| `api/license/link/start` | `{instanceId, name, url, version, secretHash}` – `secretHash` = SHA-256 (hex) eines Geheimnisses, das nur die Installation kennt | `{code, approveUrl, expiresAt}` (30 Minuten gültig) |
| `api/license/link/poll` | `{instanceId, code, secret}` | `{status: pending \| approved \| expired \| unknown, license?}` |
| `api/license/refresh` | `Authorization: Bearer <lizenz>`, `{version}` | `{status, license}` oder `{status: "unlinked"}` |
| `api/license/unlink` | `Authorization: Bearer <lizenz>` | `{ok: true}` |

`refresh` ist zugleich das Lebenszeichen der Installation (letzte Meldung und Version erscheinen in der Konsole) und
nimmt auch abgelaufene Lizenzen an, solange die Signatur stimmt.

## Lizenz-Token

`base64url(JSON) + "." + base64url(Ed25519-Signatur über den base64url-Teil)`:

```json
{
  "v": 1,
  "iss": "https://account.cyrellian.com/plazello",
  "sub": "<instanceId>",
  "account": "kunde@example.com",
  "plan": "standalone-pro",
  "planName": "Plazello Pro",
  "features": { "maxStaff": 25, "autoUpdates": true, "pluginBundles": ["premium"] },
  "status": "active",
  "plugins": ["etsy"],
  "iat": 1790000000,
  "exp": 1792592000
}
```

Die Installation prüft Signatur (öffentlicher Schlüssel des Produkts), `sub` = eigene Instanz-ID und `exp` plus
14 Tage Kulanz. `plugins` enthält alle freigeschalteten Bezahl-Plugins (aus Tarif-Paketen, Käufen und Freischaltungen).
