Skip to content

Incidents

The incident endpoints cover the full lifecycle: open → acknowledged → in_progress → resolved → verified. Auth levels per endpoint are noted inline - see Authentication for how sessions and the mobile bearer key work, and the field-value notes in Status & assets for the shared incident fields (status, severity, reporterName, slaBreached, source, isDowntime).

Auth: public read.

Query filters, all optional and combinable: ?status=&severity=&assetId=&unitId=. Merged duplicates are hidden unless you pass includeMerged=true.

[
{
"id": 17,
"title": "Elevator A2 stuck on floor 7",
"description": "",
"severity": "critical",
"status": "in_progress",
"isDowntime": true,
"assetId": 2,
"assetName": "Elevator A2",
"unitId": null,
"unitName": null,
"reporterName": "Anonymous resident",
"slaBreached": true,
"source": "web",
"createdAt": "2026-06-08T09:12:00.000Z",
"resolvedAt": null
}
]

Asset/unit names and the reporter display name are joined in; exactly one of assetId/unitId is set (except untriaged source: "email" incidents, which may have neither until staff assign one).

Auth: session or bearer key (writes require auth).

Create an incident. Accepts JSON, or multipart form data when attaching photos (photos[] file parts alongside the fields).

{
"assetId": 2,
"title": "Elevator A2 stuck on floor 7",
"description": "Car stopped between 7 and 8, alarm pressed.",
"severity": "high",
"isAnonymous": false
}
  • Provide either assetId or unitId - exactly one.
  • severity: low | medium | high | critical.
  • isAnonymous: true hides the reporter’s name from non-staff everywhere (shown as "Anonymous resident"); staff still see the real name.
  • The incident starts open; the reporter automatically follows it; source is set from how the request authenticated (web session → web, bearer → mobile).
  • Fires the incident.created webhook event and writes an audit entry.
Terminal window
curl -H "Authorization: Bearer dev-mobile-key" \
-H "Content-Type: application/json" \
-d '{"assetId":2,"title":"Elevator making grinding noise","severity":"medium"}' \
http://localhost:3000/api/incidents

Auth: public read.

The incident plus its updates (timeline) and photos:

{
"id": 17,
"title": "Elevator A2 stuck on floor 7",
"severity": "critical",
"status": "resolved",
"reporterName": "J. Park",
"slaBreached": true,
"source": "web",
"createdAt": "2026-06-08T09:12:00.000Z",
"resolvedAt": "2026-06-09T16:40:00.000Z",
"updates": [
{ "id": 88, "authorName": "M. Okafor", "body": "Vendor dispatched.", "newStatus": "in_progress", "createdAt": "" }
],
"photos": [ { "id": 31, "path": "incidents/17/a1b2.jpg", "createdAt": "" } ]
}

newStatus on an update is set when that update changed the incident’s status, null for plain comments. Photo paths resolve via GET /api/photos/<path>.

Merged incidents: requesting a merged duplicate returns its mergedInto/redirectTo target rather than a normal payload - follow it to the surviving incident (the web page does this redirect automatically).

Auth: session or bearer key. Status changes: staff only.

{ "body": "Vendor on site, parts ordered.", "newStatus": "in_progress" }

Anyone authenticated can comment (body only). Including newStatus moves the incident through its lifecycle and is restricted to roles board/manager/admin. The first transition past open stamps acknowledgedAt - the moment the SLA measures. Status changes notify followers, fire incident.status_changed, and are audited.

Auth: any signed-in user, or bearer key.

The resident verification step, valid on resolved incidents:

{ "fixed": true }
  • "fixed": true → status becomes verified (terminal).
  • "fixed": false → status returns to in_progress (reopened).

Both outcomes notify followers and write audit entries. This endpoint is deliberately open to all signed-in residents - see Following & verified fixes.

Auth: staff only.

{ "targetId": 12 }

Merges incident :id (the duplicate) into targetId: the dupe is closed out with mergedIntoId, its reporter and followers are copied to the target, the target’s timeline gains a “Merged INC-x into this incident” entry, the incident.merged webhook event fires, and the merge is audited. The dupe thereafter redirects to the target.

Auth: session or bearer key.

Registers a device for Expo push notifications (upsert on token):

{ "token": "ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]", "platform": "ios" }

platform: ios | android. Followers with registered tokens receive push on incident.created / status_changed / sla_breached / merged events relevant to incidents they follow (delivery requires EXPO_PUSH_ENABLED=true on the server).

Auth: staff only.

CSV export of the incident list. Accepts the same filters as GET /api/incidents (status, severity, assetId, unitId, includeMerged):

Terminal window
curl -b jar.txt \
"http://localhost:3000/api/export/incidents.csv?assetId=2&status=resolved" \
-o elevator-a2-resolved.csv