ROOSTby Axowl

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 · RouteWhoEffect · audit event
POST /api/org/{slug}/roost/airgap/challengeOwner{ 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/importOwner / 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 AdminThe 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=30Owner / 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

kindFieldsRule
bindnonce, 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.
statuspayload, signature — the /api/roost/report envelope, payload roost-status v1Applied 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).
usagepayload, 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.