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:
| Credential | Sent as | Used by | Scope |
|---|---|---|---|
| Session cookie | Cookie (set by sign-in) | The web app, humans | Acts as the signed-in user, with their role |
| Mobile API key | Authorization: Bearer ${MOBILE_API_KEY} | The mobile app | Acts as the “Mobile Reporter” user |
| Cron secret | Authorization: Bearer ${CRON_SECRET} | Schedulers | Only POST /api/cron/sweep and POST /api/cron/digest |
| Inbound email secret | Authorization: Bearer ${INBOUND_EMAIL_SECRET} | Your email provider’s webhook | Only POST /api/inbound-email |
Reads vs. writes
Section titled “Reads vs. writes”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.
Session cookie (web)
Section titled “Session cookie (web)”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:
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.
Mobile bearer key
Section titled “Mobile bearer key”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.
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/incidentsCron secret
Section titled “Cron secret”CRON_SECRET guards exactly two endpoints - the SLA escalation sweep and the
weekly digest:
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/digestActions 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
Section titled “Inbound email secret”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.
Outgoing webhook signatures
Section titled “Outgoing webhook signatures”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.
Failure modes
Section titled “Failure modes”- 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.
Summary of env variables
Section titled “Summary of env variables”MOBILE_API_KEY=dev-mobile-key # mobile bearer keyCRON_SECRET=dev-cron-secret # /api/cron/*INBOUND_EMAIL_SECRET=... # /api/inbound-emailBETTER_AUTH_SECRET=... # session signing - openssl rand -hex 32