# Strimivo License Server — Product Integration Guide

Use this document to add online license activation and validation to a software product.

**Base URL:** `https://license.strimivo.co.uk`  
**API prefix:** `/api/v1`  
**Content-Type:** `application/json` for all POST bodies  
**Runtime:** PHP (cPanel-friendly) or Node — the HTTP contract is the same.

---

## Overview

1. Admin creates a **product** (slug, e.g. `strimivo`) and generates a **license key**.
2. Your app collects the key from the user.
3. On first run, call **activate** with a stable `machine_id`.
4. On later launches (and periodically), call **validate** or **heartbeat**.
5. On uninstall / “deactivate this PC”, call **deactivate**.

A successful activate/validate also returns a signed **lease** token. Your app may trust that lease offline until `lease.expires_at` (default 72 hours). Online checks see revoke/suspend immediately.

Do **not** embed admin credentials in the client. Public API only needs license key + machine id + product slug.

---

## Fields

| Field | Meaning |
|--------|---------|
| `license_key` | `STRM-XXXXX-XXXXX-XXXXX-XXXXX` |
| `machine_id` | Stable unique device/install id |
| `product` | Product **slug** from admin |
| `machine_name` | Optional hostname |
| `app_version` | Optional app version |

`machine_id` must stay stable. If it changes, it counts as a new seat.

---

## Endpoints

### Health

`GET /api/v1/health` → `{ "ok": true, "runtime": "php", ... }`

### Public key (for offline lease verify)

`GET /api/v1/public-key`

### Activate

`POST /api/v1/activate`

```json
{
  "license_key": "STRM-XXXXX-XXXXX-XXXXX-XXXXX",
  "machine_id": "unique-stable-device-id",
  "machine_name": "STEPHEN-PC",
  "app_version": "1.0.0",
  "product": "strimivo"
}
```

Success:

```json
{
  "ok": true,
  "valid": true,
  "message": "License activated.",
  "license": {
    "id": "...",
    "key_tail": "XXXXX",
    "product": "strimivo",
    "product_name": "Strimivo",
    "status": "active",
    "seats_used": 1,
    "seats_max": 1,
    "features": {},
    "expires_at": null
  },
  "activation": { "id": "...", "machine_id": "...", "machine_name": "..." },
  "lease": {
    "token": "v1....",
    "expires_at": "2026-11-16T00:00:00.000Z",
    "offline_ok": true
  }
}
```

### Validate / Heartbeat

`POST /api/v1/validate`  
`POST /api/v1/heartbeat` (same as validate)

```json
{
  "license_key": "STRM-XXXXX-XXXXX-XXXXX-XXXXX",
  "machine_id": "unique-stable-device-id",
  "product": "strimivo"
}
```

If seats are limited (`seats_max > 0`), the machine must already be activated or you get `not_activated`.

### Deactivate

`POST /api/v1/deactivate`

```json
{
  "license_key": "STRM-XXXXX-XXXXX-XXXXX-XXXXX",
  "machine_id": "unique-stable-device-id",
  "product": "strimivo"
}
```

---

## Error codes

| `error` | Meaning |
|---------|---------|
| `invalid_request` | Bad/missing fields |
| `machine_required` | Need `machine_id` |
| `license_not_found` | Unknown key |
| `license_revoked` | Revoked |
| `license_suspended` | Suspended |
| `license_expired` | Past expiry |
| `product_mismatch` | Wrong product slug |
| `seat_limit` | No free seats |
| `not_activated` | Validate before activate |

Failures usually include `"ok": false, "valid": false`.

---

## Recommended app flow

```
enter key → POST /activate
every launch → POST /validate (or use lease until expires_at)
timer → POST /heartbeat
settings → POST /deactivate
```

Offline: cache last good `lease`; allow until `lease.expires_at`, then require online check.

---

## Examples

### JavaScript

```js
const BASE = "https://license.strimivo.co.uk/api/v1";

async function activate(key, machineId, product = "strimivo") {
  const res = await fetch(`${BASE}/activate`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      license_key: key,
      machine_id: machineId,
      product,
      app_version: "1.0.0",
    }),
  });
  return res.json();
}
```

### PHP client snippet

```php
function license_post(string $path, array $body): array {
  $ch = curl_init('https://license.strimivo.co.uk/api/v1' . $path);
  curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS => json_encode($body),
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 15,
  ]);
  $raw = curl_exec($ch);
  curl_close($ch);
  return json_decode($raw ?: '{}', true) ?: [];
}
```

### curl

```bash
curl -s -X POST https://license.strimivo.co.uk/api/v1/activate \
  -H "Content-Type: application/json" \
  -d '{"license_key":"STRM-...","machine_id":"dev-1","product":"strimivo"}'
```

---

## Admin (humans only)

- Console: `/admin/`
- Create products (remember the **slug**)
- Generate licenses, revoke, free seats

Never call `/api/admin/*` from the shipped product.

---

## AI checklist

1. Config: base URL + product slug  
2. Stable `machine_id` persisted locally  
3. Activate → unlock  
4. Validate/heartbeat while running  
5. Deactivate in settings  
6. Handle every `error` code with a clear UI message  
7. Optional: honor lease offline until `expires_at`
