Skip to content

Authentication

CondoPulse’s API is a set of JSON route handlers served by the web app (http://localhost:3000 in development). There are four credentials, each scoped to a job:

CredentialSent asUsed byScope
Session cookieCookie (set by sign-in)The web app, humansActs as the signed-in user, with their role
Mobile API keyAuthorization: Bearer ${MOBILE_API_KEY}The mobile appActs as the “Mobile Reporter” user
Cron secretAuthorization: Bearer ${CRON_SECRET}SchedulersOnly POST /api/cron/sweep and POST /api/cron/digest
Inbound email secretAuthorization: Bearer ${INBOUND_EMAIL_SECRET}Your email provider’s webhookOnly POST /api/inbound-email

Public reads - GET /api/status, GET /api/assets/:id, GET /api/units, GET /api/towers, and the incident list/detail reads - work with a session, a bearer key, or no auth at all. The status page is public by design.

GET /api/contacts is not public. The building directory exposes vendor phone numbers, account IDs, and the after-hours line, so it requires a session cookie or the mobile bearer key - no auth returns 401. (This is the SPEC’s “works with either” applied strictly; see the V4 contacts-privacy note.)

Writes require either a session cookie or the mobile bearer key. Staff-only writes (status changes, merges, admin CRUD, exports) additionally require the authenticated identity to have role board, manager, or admin - the docs and reference pages call this staff.

Sign in at /login (email/password, or Google SSO when configured) and the browser carries a session cookie on every request. Server-rendered pages and fetch calls from the web app authenticate this way automatically.

For quick API poking with a session, sign in via the auth endpoint and reuse the cookie jar:

Terminal window
curl -c jar.txt -X POST http://localhost:3000/api/auth/sign-in/email \
-H "Content-Type: application/json" \
-d '{"email":"manager@demo.test","password":"demo1234"}'
curl -b jar.txt "http://localhost:3000/api/export/incidents.csv?status=resolved"

Your role (resident / board / manager / admin) is attached to the session; endpoints enforce it server-side. See Roles & permissions for the capability matrix.

The mobile app sends a single shared key:

Authorization: Bearer ${MOBILE_API_KEY}

The development default is dev-mobile-key (from .env.example); set a real value in production. Bearer-authed requests act as the designated “Mobile Reporter” seed user - a real user row, so incidents created this way have a reporter, auto-follow works, and the audit log has an actor. The Mobile Reporter is not staff: the key can read everything public and create incidents/updates, but cannot change incident status, merge, or touch admin endpoints.

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

CRON_SECRET guards exactly two endpoints - the SLA escalation sweep and the weekly digest:

Terminal window
curl -X POST -H "Authorization: Bearer ${CRON_SECRET}" \
http://localhost:3000/api/cron/sweep
curl -X POST -H "Authorization: Bearer ${CRON_SECRET}" \
http://localhost:3000/api/cron/digest

Actions these trigger are audited as the system actor. Scheduling recipes (crontab and n8n) are in SLA policy & escalation and Weekly digest. Both jobs can also run without the HTTP layer at all: npm run sweep / npm run digest connect to the database directly.

INBOUND_EMAIL_SECRET guards POST /api/inbound-email, the provider-agnostic email-ingestion endpoint. Configure your email provider/forwarder to POST parsed messages there with the bearer header; the payload format and [INC-<id>] subject behavior are documented in Admin & ops and Triage queue & email-in.

Strictly the reverse direction - CondoPulse authenticating itself to your systems. Every outgoing webhook delivery is signed with the hook’s secret:

x-condopulse-signature: sha256=<HMAC-SHA256(raw body, secret)>

Verification snippet and details: Webhooks.

  • Missing/wrong bearer key or secret → 401.
  • Valid identity, insufficient role (e.g. resident calling a staff endpoint, or the mobile key calling /api/admin/...) → 403.
  • The cron and inbound-email secrets are not interchangeable with each other or with MOBILE_API_KEY - each bearer value works only on its own endpoints.
Terminal window
MOBILE_API_KEY=dev-mobile-key # mobile bearer key
CRON_SECRET=dev-cron-secret # /api/cron/*
INBOUND_EMAIL_SECRET=... # /api/inbound-email
BETTER_AUTH_SECRET=... # session signing - openssl rand -hex 32