# BB-Wertung: Lizenzabfrage

Vertrag Version 1. Basis-URL `https://tobiasjonas-ludwig.de`.

## Abfrage

`POST https://tobiasjonas-ludwig.de/api/lizenzen/checkup` mit JSON-Body, oder `GET https://tobiasjonas-ludwig.de/api/lizenzen/checkup?key=BBW-...`.

| Feld | Pflicht | Bedeutung |
|---|---|---|
| `key` | ja | Lizenzschluessel `BBW-XXXX-XXXX-XXXX`. Gross-/Kleinschreibung, Leerzeichen und Bindestriche werden toleriert. |
| `instance` | nein | Feste Kennung der Installation (max. 100 Zeichen), z. B. eine UUID, die beim ersten Start erzeugt und gespeichert wird. |
| `version` | nein | Programmversion (max. 40 Zeichen). |
| `judges` | nein | Juroren-Plaetze, die das Event braucht. Die Antwort sagt in `judges_ok`, ob die Lizenz reicht. |

```bash
curl -s -X POST https://tobiasjonas-ludwig.de/api/lizenzen/checkup \
  -H "Content-Type: application/json" \
  -d '{"key":"BBW-K7RM-4XQP-A2VN","instance":"3f1c...","version":"1.0.0","judges":7}'
```

## Antworten

Gueltig (HTTP 200):

```json
{
  "ok": true,
  "result": "ok",
  "license": {
    "key": "BBW-K7RM-4XQP-A2VN",
    "product": "bb-wertung",
    "licensee": "Musterverein e. V.",
    "judges_included": 7,
    "valid_from": "2026-10-01",
    "valid_until": "2027-09-30",
    "checked_at": "2026-10-03T18:21:07.000Z"
  },
  "judges_ok": true,
  "signature": "base64 oder null"
}
```

Nicht gueltig: HTTP 200 mit `ok: false` und `result` = `expired`, `revoked` oder `not_yet_valid`; `license` ist trotzdem gefuellt, `message` traegt einen deutschen Text fuer den Nutzer.
Unbekannter Schluessel: HTTP 404, `result: "unknown"`. Falsches Format: HTTP 400, `result: "invalid"`. Mehr als 60 Abfragen je IP und Stunde: HTTP 429, `result: "rate_limited"`. Serverfehler: HTTP 500, `result: "server_error"`, gespeicherte Antwort weiter verwenden.

## Signatur (optional)

Algorithmus Ed25519. Signiert wird `JSON.stringify` des Objekts `license` mit alphabetisch sortierten Schluesseln, ohne Leerzeichen, UTF-8. Oeffentlicher Schluessel (SPKI, Base64):

```
MCowBQYDK2VwAyEAu8YuADoP5rsBL2aHZ5kCMTskevOfGIz0zPzos5Sn27M=
```

Python-Beispiel mit `cryptography`:

```python
import base64, json
from cryptography.hazmat.primitives.serialization import load_der_public_key

PUB = load_der_public_key(base64.b64decode("MCowBQYDK2VwAyEAu8YuADoP5rsBL2aHZ5kCMTskevOfGIz0zPzos5Sn27M="))

def signature_ok(license: dict, signature: str) -> bool:
    body = json.dumps(license, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode()
    try:
        PUB.verify(base64.b64decode(signature), body)
        return True
    except Exception:
        return False
```

## Empfohlenes Verhalten im Wertungssystem

1. Einmal beim Programmstart fragen, wenn Internet da ist. Nie waehrend eines Wettkampfs blockieren.
2. Letzte Antwort mit `ok: true` samt Zeitpunkt lokal speichern. Offline gilt sie bis `valid_until`, hoechstens aber 90 Tage ab der letzten erfolgreichen Abfrage.
3. Nach 30 Tagen erneut fragen, wenn Internet da ist.
4. Netzfehler oder 5xx: gespeicherte Antwort weiter verwenden. Nur `unknown`, `revoked` und `expired` sperren.
5. Panelgroesse beim Oeffnen einer Runde gegen `judges_included` pruefen, nicht erst beim Start.

## Preis

99 Euro im Jahr mit 5 Juroren-Plaetzen, jeder weitere Platz 39 Euro im Jahr. Endpreis, keine Umsatzsteuer nach § 19 UStG. Lizenz anfragen: https://tobiasjonas-ludwig.de/kontakt
