# Licenses & Devices

> Read expiry, extend or revoke licenses, and manage device seats per app.

Canonical: https://grout.app/developer/documentation/people/licenses/

Each institution user holds one license (`student`, `faculty` or `institution` for lab machines). A license has an expiry, a maximum number of activations and a status: `active`, `expired`, `suspended` or `revoked`.

## Read

```bash
curl "$API/licenses?status=active&expires_before=2026-12-31T00:00:00Z" -H "Authorization: Bearer $KEY"
curl $API/licenses/summary -H "Authorization: Bearer $KEY"       # counts by type/status + expiring in 30 days
curl $API/licenses/lic_… -H "Authorization: Bearer $KEY"
```

## Extend

```bash
curl -X POST $API/licenses/lic_…/extend -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' -d '{ "days": 180 }'
curl -X POST $API/licenses/lic_…/extend -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' -d '{ "expires_at": "2027-06-30T00:00:00Z" }'
```

Extending an `expired` license reactivates it.

## Revoke

```bash
curl -X POST $API/licenses/lic_…/revoke -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' -d '{ "reason": "left institution" }'
```

The app signs out on its next heartbeat (≤ 5 minutes).

## Devices

Each app (`groutapp`, `groutcode`, `groutfilm`) allows **2 active device seats** per user. The oldest seat is signed out when a third device logs in.

```bash
curl $API/licenses/lic_…/devices -H "Authorization: Bearer $KEY"
curl -X POST $API/licenses/lic_…/devices/<seat_id>/revoke -H "Authorization: Bearer $KEY"
```

The response lists `seats` (per app, with device name, platform, app version, last seen) and legacy `activations`.

## Expiry automation

- `license.expired` fires when a license passes its expiry (daily sweep, or lazily when the app checks in).
- Subscribe to it and call `POST /licenses/{id}/extend` from your billing system, or let it lapse.
- `GET /licenses/summary` exposes `expiring_within_30_days` for dashboards.

## Events

`license.generated`, `license.expired`, `license.device_registered`.
