Skip to content

API reference

The public API is served by the gateway under /api. Interactive OpenAPI docs (rendered with Scalar) live at /api/docs when the gateway is running — generated from the controllers, so they can't drift — and the same document is published statically here (raw JSON at /api/docs-json on the gateway). This page is the GitHub-readable summary. How each flow works internally: sequence diagrams. The asynchronous side of the contract — domain events, brokers, delivery guarantees — lives in the event catalog.

Postman: import docs/postman/PropFlow.postman_collection.json. Run Auth > Login once — it saves the JWT to a collection variable and every other request uses it automatically; Create property / Create work order save their ids so the flow chains with no editing.

Authentication

Every business route requires Authorization: Bearer <token> (ADR-0008). Get a token:

curl -s -X POST localhost:3000/api/auth/login \
  -H 'content-type: application/json' \
  -d '{"email":"manager@propflow.dev","password":"propflow"}'
# -> { "accessToken": "...", "role": "manager" }

Demo users (override via AUTH_USERS): manager@propflow.dev, tenant@propflow.dev, tech@propflow.dev — password propflow.

401 = missing/invalid token. 403 = valid token, role not allowed. Roles: tenant, manager, technician (the actors).

Public endpoints (gateway, :3000)

Method Path Roles Description
POST /api/auth/login public Exchange credentials for a 1h JWT
GET /api/health · /api/health/ready public Liveness / readiness probes
GET /api/metrics public Prometheus metrics
GET /api/docs public This API, interactive (Swagger UI)
POST /api/work-orders tenant, manager Open a maintenance request (triage fields fill in asynchronously)
GET /api/work-orders any authenticated List, with page, limit, status, priority, propertyId filters
GET /api/work-orders/:id any authenticated One work order, including AI triage columns
PATCH /api/work-orders/:id/assign manager Assign a technician ({ assigneeId }) — the only way into assigned
PATCH /api/work-orders/:id/status technician, manager { status: in_progress \| completed \| cancelled }; invalid transitions → 409
POST /api/properties manager Register a property
GET /api/properties any authenticated List properties (page, limit)
GET /api/properties/:id any authenticated One property
GET /api/properties/:id/summary any authenticated Property + its work orders, composed; degrades gracefully (workOrdersAvailable: false)
GET /api/activity manager Audit feed, keyset-paginated (limit, cursor, workOrderId) — each entry carries actorId

Downstream errors pass through untouched (validation 400s, domain 409s); transport failures translate to 502/504.

Worked example

TOKEN=$(curl -s -X POST localhost:3000/api/auth/login \
  -H 'content-type: application/json' \
  -d '{"email":"manager@propflow.dev","password":"propflow"}' | jq -r .accessToken)

# open a request
curl -s -X POST localhost:3000/api/work-orders \
  -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' \
  -d '{"title":"Burst pipe","description":"Water everywhere","propertyId":"<uuid>","priority":"urgent"}'

# watch the audit trail (who did what, incl. the AI triage event)
curl -s "localhost:3000/api/activity?limit=10" -H "Authorization: Bearer $TOKEN"

Internal service APIs

Not reachable from outside the cluster (NetworkPolicy allows ingress from the gateway only); listed for orientation — the gateway proxies to them 1:1.

Service Port Endpoints Notes
work-orders 3001 /work-orders* Owns validation and the state machine; consumes work-order.created for AI triage
properties 3003 /properties* Owns property data
audit 3004 /activity Keyset-paginated projection of the Kafka stream
notifications 3002 (none public) Pure event consumer
all /health, /health/ready, /metrics Probes + Prometheus, per service