Status & assets
The read endpoints behind the status page, asset pages, mobile Status screen,
and the kiosk. Status, assets, units, and towers are public reads - no
auth required; session or bearer also accepted. GET /api/contacts is the
exception: it requires a session or the mobile bearer key (see
Authentication).
GET /api/status
Section titled “GET /api/status”The whole building in one call: property info, a status summary, and every asset with its uptime history. This is what the home page, the kiosk, and the mobile Status screen render.
{ "property": { "id": 1, "name": "Harborview Towers", "address": "…", "timezone": "America/Toronto" }, "summary": { "total": 9, "operational": 6, "degraded": 1, "down": 1, "maintenance": 1 }, "assets": [ { "id": 2, "name": "Elevator A2", "category": "elevator", "towerName": "Tower A", "status": "down", "uptime30": "97.81", "uptime90": "99.12", "days": [ { "date": "2026-03-15", "status": "operational" }, { "date": "2026-03-16", "status": "maintenance" }, { "date": "2026-03-17", "status": "down" } ], "maintenanceUpcoming": [ { "title": "Monthly inspection", "startsAt": "2026-06-16T10:00:00.000Z", "endsAt": "2026-06-16T12:00:00.000Z" } ] } ]}Field notes:
summary- counts of assets currently in each status;totalis the asset count.status(per asset) -operational | degraded | down | maintenance.uptime30/uptime90- uptime percentages over the trailing 30/90 days, two decimals. Derived from downtime incidents with overlapping intervals merged and maintenance windows excluded - the full math is in Reading uptime bars.days- exactly 90 entries, one per calendar day in the property’s timezone, oldest first.statusper day is the worst state that day:down | degraded | maintenance | operational(precedence in that order). Render as the slat bar.maintenanceUpcoming- declared maintenance windows in the next 30 days for this asset (recurring windows appear as their expanded occurrences).
GET /api/assets/:id
Section titled “GET /api/assets/:id”One asset in depth: the same asset fields and uptime data as /api/status,
plus the asset’s incident history, most recent first.
{ "id": 2, "name": "Elevator A2", "category": "elevator", "towerName": "Tower A", "status": "down", "description": "…", "uptime30": "97.81", "uptime90": "99.12", "days": [ { "date": "…", "status": "…" } ], "maintenanceUpcoming": [ … ], "incidents": [ { "id": 17, "title": "Elevator A2 stuck on floor 7", "severity": "critical", "status": "in_progress", "isDowntime": true, "reporterName": "Anonymous resident", "slaBreached": true, "source": "web", "createdAt": "2026-06-08T09:12:00.000Z", "resolvedAt": null } ]}For staff-authenticated callers the payload also includes the 365-day
rollups used on asset detail and the Insights tab: cost365Cents (sum of
repair costs across the asset’s incidents) and downtimeHours365 (from
the uptime engine). Non-staff callers don’t receive cost data.
Incident field notes (these apply wherever incidents appear in the API):
status-open | acknowledged | in_progress | resolved | verified.severity-low | medium | high | critical.isDowntime- whether the incident counts against the asset’s uptime.reporterName- display name;"Anonymous resident"when the reporter chose anonymity and the caller isn’t staff (staff see the real name).slaBreached-trueonce the incident has exceeded its time-to-acknowledge target.source-web | mobile | email.
GET /api/contacts
Section titled “GET /api/contacts”The building directory, in display order. Requires auth - a session
cookie or the mobile bearer key; an unauthenticated request returns 401.
The directory carries vendor phone numbers, account IDs in notes, and the
after-hours line, so it isn’t served to the anonymous public.
[ { "id": 1, "name": "M. Okafor", "label": "Property Manager", "phone": "+1 416 555 0181", "email": "manager@harborview.example", "notes": "Office hours Mon–Fri 9–5", "sortOrder": 1 }]GET /api/units and GET /api/towers
Section titled “GET /api/units and GET /api/towers”Picker data for report forms:
// GET /api/towers[ { "id": 1, "name": "Tower A" }, { "id": 2, "name": "Tower B" } ]
// GET /api/units[ { "id": 14, "towerId": 1, "number": "1204", "floor": 12 } ]GET /api/photos/<path>
Section titled “GET /api/photos/<path>”Serves uploaded incident photos through the active storage driver (local
disk or S3/R2). Photo paths come from incident payloads; thumbnails use the
.thumb.jpg variant generated at upload time.
Polling guidance
Section titled “Polling guidance”The kiosk refreshes GET /api/status every 60 seconds; that’s a sensible
ceiling for any dashboard-style consumer. Uptime percentages and day slats
only change when incidents or maintenance windows do - for event-driven
integrations, prefer webhooks (asset.status_changed,
incident.*) over tight polling.