Device ledger
Devices
| Serial | Site | Voters | Tamper | Firmware | Status |
|---|---|---|---|---|---|
| ROOST-26-0001 | Seoul HQ, vault floor | 3/3 | Armed | 1.1.0 (B) | Green |
| ROOST-26-0002 | Busan DR site | 3/3 | Armed | 1.1.0 (B) | Green |
Roost management opens only for an organization that has bought a box. Axowl ships each box to the buying organization, and that organization binds it with the certificate card shipped with the box (see Binding). Until a box is bound, every /api/org/{slug}/roost/* route answers 404 ROOST_NOT_ENABLED, except the five that lead to binding: GET /summary, POST /bind/challenge, POST /bind, POST /airgap/challenge and POST /airgap/import.
Who can open Roost
Only the organization Owner and members holding the fixed Roost Admin role (ROOST_ADMIN) can open the Roost section and call its API. The role carries one scope, view.roost, and is created in every organization like the other preset roles.
- Fixed. Its name, description and permissions cannot be changed and the role cannot be deleted (
403 ROLE_FIXED). - Owner assigns it. Only the Owner can assign or revoke it (
403 OWNER_REQUIREDfor anyone else, Admins included). - Admins are not included.
view.roostis an Owner-only scope likeview.billing: an Admin opens Roost only after the Owner gives them the Roost Admin role. - Seal actions stay with the Owner. Binding, pairs, replacement, envelopes, the enrollment ceremony, transfers and re-arm need the Owner and a passkey seal, as before.
- Auditors see the audit screens only. A Roost Auditor opens the audit log, the auditor list and the log-export settings, nothing else.
Every Roost box an organization owns has a ledger row: serial, model, site, license kind, lifecycle state, the pair it belongs to and its latest signed status report. These endpoints read that ledger. All require the organization Owner or a Roost Admin (403 otherwise). Writes are described in Binding and replacement.
| Method · Route | Returns |
|---|---|
GET /api/org/{slug}/roost/summary | { deviceCount, pairCount, green, yellow, red, gray, openCases, unacknowledgedRed, awaitingBind, boundCount } — awaitingBind counts boxes shipped to this organization and not bound yet. Answers even before a box is bound. |
GET /api/org/{slug}/roost/devices | Array of device rows (below) with status = latest report or null |
GET /api/org/{slug}/roost/devices/{serial} | { device, status, pair, events[≤50], enrollment, envelopes, rollouts, maintenance }; 404 ROOST_DEVICE_NOT_FOUND |
GET /api/org/{slug}/roost/pairs | Pairs: deviceA, deviceB, transient (third box during replacement), replacementStep 0–6, envelopes[] |
GET /api/org/{slug}/roost/events?serial=&limit= | Event timeline, newest first (limit 1–500, default 100) |
GET /api/org/{slug}/roost/enrollments | Enrollment ceremonies: arrival check → measurement compare → quorum registration |
GET /api/org/{slug}/roost/roles | { roles[], assignments[], policies[] } — role assignments and quorum per action |
GET /api/org/{slug}/roost/maintenance | Replacement, RMA, disposal and module-swap cases |
GET /api/org/{slug}/roost/firmware | { releases[], rollouts[] } — signed images and A/B rollout state per box |
Device row
{
"serial": "ROOST-26-0001",
"displayName": "Seoul HQ A",
"model": "ROOST-3U",
"siteName": "Seoul head office, vault floor",
"country": "KR",
"siteKind": "CustomerSite",
"licenseKind": "Connected",
"lifecycleState": "Active",
"pairId": "…",
"isAxowlOperated": false,
"isDemoUnit": false,
"shippedAt": "2026-08-20T00:00:00Z",
"boundAt": "2026-08-25T00:00:00Z",
"lastReportAt": "2026-10-19T08:00:00Z",
"status": {
"color": "Green",
"quorumOk": true, "votersOnline": 3,
"psu1Ok": true, "psu2Ok": true, "fansFailed": 0,
"battery1Mv": 3610, "battery2Mv": 3605, "activeBattery": 1,
"zeroizeState": "Armed", "keysPresent": true,
"ntpOk": true, "driftMs": 8,
"activeSlot": "B", "activeVersion": "1.1.0",
"signCounter": 89261,
"reportedAt": "2026-10-19T08:00:00Z",
"signatureVerified": true
}
}
lifecycleState:Factory·Shipped·Enrolling·Active·Degraded(running 2-of-3) ·Zeroized·RmaReturned·Retired·Destroyed.licenseKind:None·Dedicated(Axowl rack) ·Connected(customer site + cloud) ·AirGapped.status.color:Green·Yellow(warning, after 5 minutes) ·Red(alarm, immediate) ·Gray(no report for 10 minutes).statusisnulluntil the box has reported.zeroizeState:Armed(keys held) ·Soft(memory erased after a power loss or failed self-test; keys return after three-party authentication) ·Hard(permanent; factory reseal).
The full status in /devices/{serial} adds the three voters (KV260, PolarFire, MachXO3D) with their key ids, the watcher board, mesh resistance ratio, lid / light / EFP switches, seven chip temperatures, board temperature, 12 V / 5 V / 3.3 V, RTC state and the measured hash of each firmware slot.
Events
kind: Tamper · Zeroize · Degraded · Recovered · BatteryLow · BatterySwapped · PowerLoss · PsuFailed · FanFailed · TimeDrift · Rearm · Bound · Enrolled · ReplacementStep · EnvelopeIssued · EnvelopeRevoked · FirmwareCommitted · FirmwareRolledBack · Moved · Disposal. severity: Red · Yellow · Info. auditEventRef links a sealed audit record when the event was produced by a sealed action.
Replacement steps
A failed box is never opened on site. replacementStep on a pair: 1 degraded 2-of-3 → 2 new box staged → 3 joint registration (3 physical keys + Owner quorum seal + Axowl ticket; the pair runs as three boxes) → 4 envelope verified → 5 old box removed → 6 factory resealed.
Audit event names reserved for the ledger (emitted as each write lands): roost.device.bound, roost.device.transferred, roost.device.lifecycle_changed, roost.enrollment.completed, roost.pair.replacement_step, roost.envelope.issued, roost.envelope.revoked, roost.alert.raised, roost.alert.cleared, roost.role.assigned, roost.maintenance.opened, roost.disposal.certified, roost.firmware.released, roost.firmware.committed, roost.test.recorded.