Docs
Air-gapped sites
A box on an air-gapped site (license kind AirGapped) never talks to the server. The same steps run as files carried by USB or QR. Status reports fill the latest status and the graphs; usage reports become one row per period; the box is never shown gray for "no report".
| Method · Route | Who | Effect · audit event |
|---|
POST /api/org/{slug}/roost/airgap/challenge | Owner | { serial, productCert } → the challenge file { typ: "roost-airgap-challenge", v: 1, serial, org, nonce, expiresAt, payloadToSign }. Same rules and errors as /bind/challenge. |
POST /api/org/{slug}/roost/airgap/import | Owner / Roost Admin (a bind entry needs the Owner seal roost.bind, subject RoostBindSubject(slug, serial, nonce)) | { file, stepUpAssertion? } where file is the JSON text the box wrote: { typ: "roost-airgap", v: 1, serial, entries[] }. Returns per-entry results and the file's SHA-256. roost.airgap.imported |
GET /api/org/{slug}/roost/airgap/export?serial= | Owner / Roost Admin | The file to carry to the box: { serial, file, fileHash, keyFingerprint, content }. file = base64url(payload).base64url(signature) with the Roost identity key (verify with GET /api/roost/identity-public-key); content = { typ: "roost-airgap-out", v: 1, serial, org, issuedAt, lifecycleState, boundAt, enrollment, timePolicy, rollouts[], expectedMeasurements }. 503 ROOST_IDENTITY_KEY_MISSING. roost.airgap.exported |
GET /api/org/{slug}/roost/usage?days=30 | Owner / Roost Admin | { from, to, boxes[], days[{ date, signs }], windowTotal, reports[] } — daily seal counts are differences of each box's monotonic sign counter; reports are the usage reports imported from files. |
Import file entries
kind | Fields | Rule |
|---|
bind | nonce, boxSignature (ECDSA P-256 r‖s, base64url, over the challenge payload) | At most one, processed first. The whole file is refused when the seal or the signature fails. |
status | payload, signature — the /api/roost/report envelope, payload roost-status v1 | Applied in time order. Older than the last accepted report → REPORT_REPLAYED; more than 5 minutes in the future → REPORT_CLOCK_SKEW. Past times are accepted (that is the point). |
usage | payload, signature — payload { typ: "roost-usage", v: 1, serial, at, periodStart, periodEnd, counterStart, counterEnd, servers } (times in Unix seconds) | One row per box and periodStart; a repeated report answers duplicate: true. signs = counterEnd − counterStart. |