Skip to content

Admin & ops

The machine-facing operational endpoints: staff CRUD, email ingestion, the scheduled jobs, and the optional AI triage assist. Auth per endpoint varies - each section states it; details in Authentication.

Auth: staff (session with role board/manager/admin).

GET /api/admin/webhooks list
POST /api/admin/webhooks create
PATCH /api/admin/webhooks/:id update (any subset of fields)
DELETE /api/admin/webhooks/:id delete

Create/update body:

{
"url": "https://n8n.example/webhook/condopulse",
"secret": "whsec_…",
"events": ["incident.created", "incident.sla_breached", "asset.status_changed"],
"active": true
}

Responses include delivery health: lastStatus (e.g. "200" or "failed: timeout") and lastFiredAt. Event names, payload shape, and signature verification are on the Webhooks page; the admin UI equivalent is /admin → Webhooks.

Maintenance window CRUD - /api/admin/maintenance

Section titled “Maintenance window CRUD - /api/admin/maintenance”

Auth: staff.

GET /api/admin/maintenance list
POST /api/admin/maintenance create
PATCH /api/admin/maintenance/:id update
DELETE /api/admin/maintenance/:id delete

Create/update body:

{
"assetId": 7,
"title": "Monthly fire alarm test",
"startsAt": "2026-07-01T10:00:00.000Z",
"endsAt": "2026-07-01T11:00:00.000Z",
"recurrence": "monthly",
"notes": "First Tuesday, building-wide audible test"
}

recurrence: none | weekly | monthly. Windows are excluded from uptime math and paint blue day slats - semantics in Maintenance windows.

Auth: Authorization: Bearer ${INBOUND_EMAIL_SECRET}.

Provider-agnostic email ingestion. Body:

{
"from": "resident@example.com",
"subject": "Re: [INC-42] Elevator A2 stuck on floor 7",
"text": "Plain-text body…",
"html": "<p>Optional HTML body</p>",
"attachments": [
{ "filename": "photo.jpg", "contentBase64": "", "contentType": "image/jpeg" }
]
}

Routing by subject:

  • Subject contains [INC-<id>] → appended to that incident as a timeline update; attachments become incident photos.
  • Otherwise → creates a new untriaged incident: source: "email", title = subject, description = text, severity medium, no asset/unit yet (the one permitted exception to the one-of rule, until staff triage it in /admin → Triage).

Processing is audited as the system actor. Wiring examples (Mailgun, n8n IMAP) in Triage queue & email-in.

Auth: Authorization: Bearer ${CRON_SECRET} for both. Each also has a CLI twin that hits the database directly (no server needed).

The SLA escalation sweep. Finds incidents still open past their time-to-acknowledge target (critical 4 h / high 24 h / medium 72 h / low 168 h, env-overridable via SLA_HOURS_*) and, per incident: bumps severity one level (max critical, once ever per incident), emails board+, notifies followers, fires incident.sla_breached webhooks, and audits. Run hourly. Full policy: SLA policy & escalation.

POST /api/cron/digest (CLI: npm run digest)

Section titled “POST /api/cron/digest (CLI: npm run digest)”

The weekly board digest: per-tower and per-asset downtime hours for the week, new/resolved/verified counts, oldest unacknowledged incidents, and upcoming maintenance - emailed to board/manager/admin users (or written to .data/outbox/*.eml.html without SMTP). Run weekly. Contents and schedule recipes: Weekly digest.

Terminal window
curl -X POST -H "Authorization: Bearer ${CRON_SECRET}" \
https://your-condopulse.example/api/cron/sweep

Auth: signed-in session or bearer key. Availability: only when the server has ANTHROPIC_API_KEY set and the ai_triage feature flag is enabled; otherwise the endpoint reports disabled and the report form hides the suggestion panel entirely.

Request:

{ "title": "Elevator grinding", "description": "A2 makes a loud grinding noise approaching the lobby…" }

Response - a triage suggestion for the report form:

{
"assetId": 2,
"severity": "high",
"isDuplicateOf": 17,
"rationale": "Matches Elevator A2; an open incident describes the same noise."
}

All fields except severity are optional in practice (assetId and isDuplicateOf appear only when there’s a confident match). The endpoint is built to never break the form: failures and timeouts return an empty suggestion rather than an error, and the UI debounces calls (after typing pauses, minimum description length applies). Suggestions are advisory - the reporter applies or ignores them.

  • GET /api/export/incidents.csv (staff) - with the incident endpoints: Incidents.
  • GET /r/[assetId] - public QR redirect to the pre-filled report form: QR stickers.
  • Outgoing webhook deliveries - payloads, events, signatures: Webhooks.

Every write documented on this page - CRUD, email ingestion, sweep and digest runs - records an audit-log entry (human actor for staff calls, system actor for secret-authenticated machine calls). The log is append-only and admin-visible at /admin → Audit; see Exports & audit log.