Skip to main content

API Reference

PMFriend's HTTP API. Everything under /api/v1/* on https://app.pmfriend.com.

Authentication

Two auth styles:

  1. Session cookie (PM-facing). mypmf_session HS384 JWT set on /api/v1/auth/login or /api/v1/auth/register. 14-day TTL, HttpOnly, Secure. Pass back on every request.
  2. Magic-link token (contractor / tenant). Token in the URL path: /api/v1/jobs/{token}. SHA-256-hashed server-side. No session cookie needed.

All data is scoped to the calling agency via PostgreSQL row-level security. No explicit agencyId parameter — the database enforces the boundary.

Auth

MethodPathPurpose
POST/auth/registerCreate agency + first admin. Returns session cookie.
POST/auth/loginEmail + password. Returns session cookie.
POST/auth/logoutClears session cookie (both Lax + Partitioned variants).
POST/auth/demo-loginDrops caller into the Sandbox Agency. Sets Partitioned cookie for cross-site iframe.
GET/auth/meReturns current user + agency.

Google SSO via Spring Security: /oauth2/authorization/google 302s to Google, back to /login/oauth2/code/google.

Maintenance

MethodPathPurpose
GET/maintenance-requestsList. Filter by status, urgency, property.
GET/maintenance-requests/{id}Single request
POST/maintenance-requestsCreate (MANUAL channel)
POST/maintenance-requests/{id}/suggest-triageAI triage, non-mutating
POST/maintenance-requests/{id}/apply-triageCommit category + urgency, starts SLA
POST/maintenance-requests/{id}/detect-duplicateAI duplicate check
POST/maintenance-requests/{id}/convert-to-work-orderConvert with scope + cost ceiling
POST/report/{token}Public tenant submission (no auth)

Work orders

MethodPathPurpose
GET/work-ordersList
GET/work-orders/{id}Detail
POST/work-orders/draft-scopeAI scope drafter, non-mutating
GET/work-orders/{id}/suggested-contractorsRanked contractor picker
POST/work-orders/{id}/dispatchSend magic link to contractor
GET/jobs/{token}Contractor view (public, token-gated)
POST/jobs/{token}/transitionsContractor status change
POST/jobs/{token}/invoicesContractor invoice upload

Properties

MethodPathPurpose
GET/propertiesList
GET/properties/{id}Detail
POST/propertiesCreate
PATCH/properties/{id}Update
GET/properties/{id}/ai-suggestionsAI-derived next actions
POST/properties/{id}/ai-actions/create-work-orderOne-click chain: submit → triage → scope → WO
GET/properties/{id}/complianceCompliance register
POST/properties/import-csvBulk import (multipart)

Contractors

MethodPathPurpose
GET/contractorsList
GET/contractors/{id}Detail
POST/contractorsCreate
PATCH/contractors/{id}Update
POST/contractors/import-csvBulk import (multipart)

Owners

MethodPathPurpose
GET/ownersList
GET/owners/{id}Detail
POST/ownersCreate
POST/owners/{id}/digests/draftAI digest drafter
GET/owners/{id}/digestsSent-digest log

Tenants

MethodPathPurpose
GET/tenantsList
POST/tenantsCreate
POST/tenants/import-csvBulk import (multipart)

Compliance

MethodPathPurpose
GET/compliance/tasksAll tasks across agency
POST/compliance/tasks/{id}/completeMark done, auto-schedules next

All non-mutating — drafts are returned to the caller; nothing is persisted.

MethodPathPurpose
POST/notices/draft-arrearsState-aware rent-arrears notice (friendly reminder / Notice to Remedy Breach / Notice of Termination). See Arrears Ladder
POST/notices/draft-entry-noticeState-aware entry notice with required statutory notice period per reason. See Entry Notice
POST/case-packs/draftTribunal/VCAT case pack: pulls maintenance + work-order history at a property within a date range, returns Statement of Facts + chronology + relief. See Case Pack
POST/inspections/draftRoutine inspection report: PM-supplied room-by-room checklist → AU-English prose + extracted safety flags + follow-ups. See Inspection Report Writer

Notifications

MethodPathPurpose
GET/notifications?scope=MINE|ALL&unreadOnly=&limit=Per-user feed. MINE (default) returns assigned-to-me + agency-wide; ALL returns every row in the agency
POST/notifications/{id}/readMark read for the calling user
POST/notifications/{id}/dismissDismiss for the calling user
POST/notifications/read-all?scope=MINE|ALLMark every visible-and-unread notification read for the calling user, scoped per scope

Settings & Team

MethodPathPurpose
GET/agencies/meCurrent agency profile
PATCH/agencies/meUpdate name, ABN, logoUrl
GET/team-invitesPending invite list
POST/team-invitesIssue invite
DELETE/team-invites/{id}Cancel invite
POST/team-invites/acceptAccept invite with password set

Rate limits

  • /report/{token} — 5 submissions per token per hour
  • /jobs/{token}/* — 30 requests per token per minute
  • Authenticated endpoints — 300 requests per minute per session

Response format

JSON throughout. Errors look like:

{
"error": "validation_failed",
"message": "Human-readable summary",
"details": [
{ "field": "ownerApprovalThresholdCents", "message": "must be positive" }
]
}

HTTP status codes mean what they say on the tin (400 validation, 401 auth, 403 forbidden, 404 not found, 409 conflict, 429 rate-limited, 5xx server).

Versioning

/api/v1/* is the current namespace. Breaking changes get /api/v2/*. Non-breaking additions (new fields, new endpoints) happen in-place.

See also