# 01 — API Reference (exact, live contract)

Base URL (embedded in the shipped Windows + Android apps): **`https://license.kotsch.tech`**

All bodies are JSON (`Content-Type: application/json`). All responses are JSON.
These four endpoints are **fixed** — the apps are already compiled against them.

---

## POST `/api/activate`

Redeem a key on this device and receive a signed token.

**Request**
```json
{
  "key": "CONV-ABCDE-FGHIJ-KLMNO",
  "appId": "convertly",
  "fingerprint": "9f3c…64-hex…",
  "deviceName": "Arthur's PC"
}
```
| Field | Notes |
|-------|-------|
| `key` | user input; normalize before hashing (strip non‑alphanumerics, uppercase) |
| `appId` | always `"convertly"` for this product (Windows + Android share it) |
| `fingerprint` | 64‑char lowercase hex; stable per device (see below) |
| `deviceName` | free text, for the admin device list |

**Success `200`**
```json
{ "token": "<base64url-payload>.<base64url-signature>", "tier": "standard", "name": "Arthur Kotsch" }
```
The app stores `token` locally and from then on verifies it **offline**.

**Errors**
| HTTP | body `error` | meaning (app shows) |
|------|--------------|---------------------|
| 400 | `missing_fields` | request incomplete |
| 404 | `invalid_key` | key unknown for this app |
| 403 | `revoked` | license status ≠ active |
| 403 | `device_limit` (+ `maxDevices`) | all device slots used |
| 500 | `server_error` | DB/transaction failure |

**curl**
```bash
curl -sX POST https://license.kotsch.tech/api/activate \
  -H 'Content-Type: application/json' \
  -d '{"key":"CONV-ABCDE-FGHIJ-KLMNO","appId":"convertly","fingerprint":"<64hex>","deviceName":"Test"}'
```

---

## POST `/api/refresh`

Renew the lease (extends `exp`) and re‑check revocation + device binding. Called at app startup
in the background; also when the local lease has expired.

**Request**
```json
{ "token": "<the current token>" }
```

**Success `200`**
```json
{ "token": "<fresh token>", "tier": "standard" }
```

**Errors**
| HTTP | body `error` | app behaviour |
|------|--------------|---------------|
| 400 | `bad_token` | token unpar. – ignore/keep grace |
| 403 | `revoked` | app clears token → back to activation |
| 403 | `device_not_bound` | this device slot was removed → back to activation |

> The apps treat any **403** on refresh as "license no longer valid on this device" and delete the
> local token. Non‑403 failures (offline, 5xx) keep the current token within the 14‑day offline grace.

---

## POST `/api/deactivate`

Log this device out and **free its slot** (so another device can activate).

**Request**
```json
{ "token": "<the current token>" }
```

**Success `200`**
```json
{ "ok": true }
```
Deletes the `devices` row matching `(payload.lic, payload.dev)`. Idempotent — always `200`.

---

## GET `/health`

```bash
curl -s https://license.kotsch.tech/health   # → {"ok":true}
```

---

## How the app builds `fingerprint`

A stable, non‑reversible per‑device id. **Both platforms** use:

```
fingerprint = sha256_hex( appId + "|" + rawDeviceId )        // 64 lowercase hex
```
- **Windows**: `rawDeviceId` = registry `HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid`.
- **Android**: `rawDeviceId` = `Settings.Secure.ANDROID_ID`.

The server treats `fingerprint` as an opaque string — it just stores/compares it. Same person on a PC
and a phone produces two different fingerprints → counts as 2 of the 3 device slots.

---

## Token payload (claims the app reads)

Decoded `base64url(payload)` is JSON:
```json
{
  "sub": "cus_…",       // customer id
  "lic": "lic_…",       // license id
  "app": "convertly",   // must equal the app's appId
  "tier": "standard",
  "dev": "<fingerprint>",// must equal this device's fingerprint
  "iat": 1751000000,     // issued-at (unix seconds)
  "exp": 1782536000,     // lease expiry (unix seconds); server uses 365-day lease
  "name": "Arthur Kotsch",
  "email": "arthur@example.com"
}
```
The app validates: signature valid **AND** `app == appId` **AND** `dev == fingerprint` **AND**
(`now < exp` **OR** within 14‑day offline grace). `name` drives the "Hallo, {first name}!" greeting.
