UrbanIQ
Pricing About Contact
Society Login PMF Login Get Started

API Documentation

REST API v1 — Integrate SystoMax into your mobile apps, websites, and third-party systems. 245 routes across 6 personas.

Base: /api/v1 Auth: Bearer Token Society / PMF Tokens

Table of Contents

Quick start

Authenticate once, keep the returned token, and send it as a Bearer token on every subsequent request. All examples use placeholders — replace the host, credentials, and token with your own values.

Base URL
https://your-society.example.com/api/v1
Content type
Send Accept: application/json and Content-Type: application/json.
Token lifetime
Society tokens: 1 year. PMF tokens: 30 days.
1. Authenticate a society user
curl -X POST "https://your-society.example.com/api/v1/auth/login" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "admin@example.com",
    "password": "YOUR_PASSWORD",
    "code": "green-valley-a1b2"
  }'
const response = await fetch("https://your-society.example.com/api/v1/auth/login", {
  method: "POST",
  headers: {
    "Accept": "application/json",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    email: "admin@example.com",
    password: "YOUR_PASSWORD",
    code: "green-valley-a1b2"
  })
});

const data = await response.json();
console.log(data.token);
use Illuminate\Support\Facades\Http;

$response = Http::acceptJson()->post('https://your-society.example.com/api/v1/auth/login', [
    'email' => 'admin@example.com',
    'password' => 'YOUR_PASSWORD',
    'code' => 'green-valley-a1b2',
]);

$token = $response->throw()->json('token');
200 OK Sample response
{
  "token": "YOUR_SOCIETY_API_TOKEN",
  "user": {
    "id": 42,
    "name": "Jane Doe",
    "email": "admin@example.com",
    "type": "admin"
  },
  "tenant": {
    "id": 5,
    "society_name": "Green Valley Housing"
  }
}
2. Call an authenticated endpoint
curl "https://your-society.example.com/api/v1/society/dashboard" \
  -H "Authorization: Bearer YOUR_SOCIETY_API_TOKEN" \
  -H "Accept: application/json"
const response = await fetch("https://your-society.example.com/api/v1/society/dashboard", {
  headers: {
    "Authorization": "Bearer YOUR_SOCIETY_API_TOKEN",
    "Accept": "application/json"
  }
});

const dashboard = await response.json();
console.log(dashboard.total_units);
use Illuminate\Support\Facades\Http;

$response = Http::withToken('YOUR_SOCIETY_API_TOKEN')
    ->acceptJson()
    ->get('https://your-society.example.com/api/v1/society/dashboard');

$dashboard = $response->throw()->json();
200 OK Sample response
{
  "total_units": 120,
  "occupied_units": 96,
  "vacant_units": 24,
  "open_complaints": 3,
  "total_residents": 148,
  "pending_invoices": 425000,
  "collected_amount": 12850000
}

1. Authentication

Public endpoints — no token required. Use /auth/login to obtain a society Bearer API token, or /auth/pmf-login to obtain a PMF API token. Tokens must be sent on every authenticated request.

POST /api/v1/auth/login Obtain a society Bearer token
Request Body
ParameterTypeRequiredDescription
emailstringYesUser email address
passwordstringYesUser password
codestringNo*Tenant code or tenant ID. Required when the request host does not already resolve a society.
personastringNostaff, resident, or security. Validates the account type.

* On a mapped society domain, the tenant is resolved from the host and code may be omitted. On a shared or central host, send code.

POST /api/v1/auth/pmf-login Obtain a PMF Bearer token
Request Body
ParameterTypeRequiredDescription
emailstringYesUser email address
passwordstringYesUser password
codestringYesPMF code.
personastringYesstaff, owner, or tenant.

PMF tokens are valid for 30 days. Tokens with an owner or tenant persona cannot call staff endpoints.

Authenticate a PMF user
curl -X POST "https://your-pmf.example.com/api/v1/auth/pmf-login" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "owner@example.com",
    "password": "YOUR_PASSWORD",
    "code": "the-property-co",
    "persona": "owner"
  }'
const response = await fetch("https://your-pmf.example.com/api/v1/auth/pmf-login", {
  method: "POST",
  headers: {
    "Accept": "application/json",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    email: "owner@example.com",
    password: "YOUR_PASSWORD",
    code: "the-property-co",
    persona: "owner"
  })
});

const data = await response.json();
console.log(data.token);
use Illuminate\Support\Facades\Http;

$response = Http::acceptJson()->post('https://your-pmf.example.com/api/v1/auth/pmf-login', [
    'email' => 'owner@example.com',
    'password' => 'YOUR_PASSWORD',
    'code' => 'the-property-co',
    'persona' => 'owner',
]);

$token = $response->throw()->json('token');
200 OK Sample response
{
  "token": "YOUR_PMF_API_TOKEN",
  "user": {
    "id": 17,
    "name": "Alex Owner",
    "email": "owner@example.com",
    "type": "owner",
    "persona": "owner"
  },
  "pmf": {
    "id": 4,
    "company_name": "The Property Co"
  }
}
POST /auth/register — Register new user (requires feature: allow_signup)
Request Body
ParameterTypeRequiredDescription
namestringYesFull name
emailstringYesEmail address
passwordstringYesPassword (min 8 characters)
society_namestringYesName of the society to create.
phonestringNoContact phone number (max 20 characters).
society_typestringNoOne of housing_society, apartment_complex, commercial_plaza, gated_community, mixed_community.
plan_codestringNoActive plan code. Defaults to the starter plan.

201 Created returns the new tenant, an admin user, and a token. Requires the allow_signup feature flag.

GET /auth/me — Get authenticated user profile

Returns the authenticated society user's profile. Requires Authorization: Bearer YOUR_SOCIETY_API_TOKEN.

POST /auth/logout — Revoke current token

Revokes the current society API token. Requires Authorization: Bearer YOUR_SOCIETY_API_TOKEN.

// Society API token — used on /auth/me, /auth/logout, /society/* Authorization: Bearer YOUR_SOCIETY_API_TOKEN // PMF API token — use on /pmf/*; either header is accepted Authorization: Bearer YOUR_PMF_API_TOKEN X-PMF-Token: YOUR_PMF_API_TOKEN // Tokens are stored as SHA-256 hashes. Query-string tokens are not accepted. // PMF personas use their own profile endpoints under /pmf/{persona}/profile.

2. Society API read-only

Read-only endpoints for society data. Authenticate with Authorization: Bearer YOUR_SOCIETY_API_TOKEN. All queries are scoped to the authenticated tenant.

List society units
curl "https://your-society.example.com/api/v1/society/units" \
  -H "Authorization: Bearer YOUR_SOCIETY_API_TOKEN" \
  -H "Accept: application/json"
const response = await fetch("https://your-society.example.com/api/v1/society/units", {
  headers: {
    "Authorization": "Bearer YOUR_SOCIETY_API_TOKEN",
    "Accept": "application/json"
  }
});

const units = await response.json();
console.log(units);
use Illuminate\Support\Facades\Http;

$units = Http::withToken('YOUR_SOCIETY_API_TOKEN')
    ->acceptJson()
    ->get('https://your-society.example.com/api/v1/society/units')
    ->throw()
    ->json();
200 OK Sample response
[
  {
    "id": 101,
    "unit_number": "A-101",
    "block_name": "Block A",
    "status": "occupied",
    "monthly_rent": 45000
  },
  {
    "id": 102,
    "unit_number": "A-102",
    "block_name": "Block A",
    "status": "vacant",
    "monthly_rent": 42000
  }
]
GET /society/dashboard — Dashboard summary — units, residents, invoices, complaints, bookings
GET /society/units — List all units — flat array, no pagination or filters
GET /society/residents — List all residents — flat array, no pagination or filters
GET /society/invoices — List invoices (?status=, ?unit_id=; paginated 20/page)
GET /society/payments — List payment records — paginated 20/page, no filters
GET /society/complaints — List complaints — paginated 20/page, no filters
GET /society/announcements — List active announcements — flat array
GET /society/visitors — List visitors — paginated 20/page, no filters
GET /society/bookings — List facility bookings — paginated 20/page, no filters
GET /society/reports/summary — Dashboard summary report
GET /society/polls — List active polls
GET /society/polls/{id} — Get poll details with options
POST /society/polls/{id}/vote — Cast vote on a poll
GET /society/polls/{id}/results — Get poll results

3. Society Staff API staff

Full CRUD for society staff. Same society Bearer token. Prefix: /api/v1/society/staff

Staff dashboard
curl "https://your-society.example.com/api/v1/society/staff/dashboard" \
  -H "Authorization: Bearer YOUR_SOCIETY_API_TOKEN" \
  -H "Accept: application/json"
const response = await fetch("https://your-society.example.com/api/v1/society/staff/dashboard", {
  headers: {
    "Authorization": "Bearer YOUR_SOCIETY_API_TOKEN",
    "Accept": "application/json"
  }
});

const dashboard = await response.json();
use Illuminate\Support\Facades\Http;

$dashboard = Http::withToken('YOUR_SOCIETY_API_TOKEN')
    ->acceptJson()
    ->get('https://your-society.example.com/api/v1/society/staff/dashboard')
    ->throw()
    ->json();
200 OK Sample response
{
  "total_units": 120,
  "occupied_units": 96,
  "vacant_units": 24,
  "open_complaints": 3,
  "open_maintenance": 7,
  "pending_invoices": 425000,
  "collected_ytd": 12850000,
  "total_residents": 148,
  "today_visitors": 12,
  "upcoming_bookings": 4
}
Record an invoice payment
curl -X POST "https://your-society.example.com/api/v1/society/staff/payments" \
  -H "Authorization: Bearer YOUR_SOCIETY_API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "invoice_id": 501,
    "amount": 45000,
    "payment_method": "cash",
    "reference": "REC-2026-001"
  }'
const response = await fetch("https://your-society.example.com/api/v1/society/staff/payments", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_SOCIETY_API_TOKEN",
    "Accept": "application/json",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    invoice_id: 501,
    amount: 45000,
    payment_method: "cash",
    reference: "REC-2026-001"
  })
});

const payment = await response.json();
use Illuminate\Support\Facades\Http;

$payment = Http::withToken('YOUR_SOCIETY_API_TOKEN')
    ->acceptJson()
    ->post('https://your-society.example.com/api/v1/society/staff/payments', [
        'invoice_id' => 501,
        'amount' => 45000,
        'payment_method' => 'cash',
        'reference' => 'REC-2026-001',
    ])->throw()->json();
201 Created Sample response
{
  "id": 9001,
  "message": "Payment recorded"
}
GET /society/staff/dashboard — Staff dashboard — counts, alerts, recent activities
GET /society/staff/buildings — List buildings
GET /society/staff/blocks — List blocks
GET /society/staff/phases — List phases
GET /society/staff/unit-categories — List unit categories
GET /society/staff/plot-categories — List plot categories
GET /society/staff/units — List units (?status=, ?building_id=, ?search=)
PUT /society/staff/units/{id}/status — Update unit status
GET /society/staff/plots — List plots
GET /society/staff/residents — List residents (?search=, ?per_page=)
GET /society/staff/staff — List staff users
GET /society/staff/roles — List roles with permissions
GET /society/staff/invoices — List invoices (?status=, ?type=, ?unit_id=, ?from=, ?to=)
POST /society/staff/invoices — Create invoice
PUT /society/staff/invoices/{id}/status — Update invoice status
GET /society/staff/payments — List payments (?status=, ?method=)
POST /society/staff/payments — Record payment
GET /society/staff/complaints — List complaints (?status=, ?priority=)
PUT /society/staff/complaints/{id}/status — Update complaint status
GET /society/staff/maintenance — List maintenance requests (?status=, ?type=)
PUT /society/staff/maintenance/{id}/status — Update maintenance status
GET /society/staff/visitors — List visitors (?status=)
POST /society/staff/visitors — Create visitor entry
PUT /society/staff/visitors/{id}/status — Update visitor status (approve/reject/check-in/check-out)
GET /society/staff/bookings — List bookings (?status=, ?facility_id=)
PUT /society/staff/bookings/{id}/status — Approve/reject/cancel booking
GET /society/staff/facilities — List facilities
GET /society/staff/announcements — List announcements
POST /society/staff/announcements — Create announcement
GET /society/staff/vendors — List vendors
GET /society/staff/assets — List assets
GET /society/staff/documents — List documents
GET /society/staff/contracts — List tenancy contracts
GET /society/staff/move-ins — List move-in requests
PUT /society/staff/move-ins/{id}/status — Approve/reject move-in
GET /society/staff/parking-buildings — List buildings with parking floors
GET /society/staff/notifications — List notifications
PUT /society/staff/notifications/{id}/read — Mark notification as read
GET /society/staff/profile — Get staff profile
PUT /society/staff/profile — Update staff profile
POST /society/staff/change-password — Change password

4. Society Resident API portal

Customer portal for society residents/owners. Society Bearer token. Prefix: /api/v1/society/resident

Resident dashboard
curl "https://your-society.example.com/api/v1/society/resident/dashboard" \
  -H "Authorization: Bearer YOUR_SOCIETY_API_TOKEN" \
  -H "Accept: application/json"
const response = await fetch("https://your-society.example.com/api/v1/society/resident/dashboard", {
  headers: {
    "Authorization": "Bearer YOUR_SOCIETY_API_TOKEN",
    "Accept": "application/json"
  }
});

const dashboard = await response.json();
use Illuminate\Support\Facades\Http;

$dashboard = Http::withToken('YOUR_SOCIETY_API_TOKEN')
    ->acceptJson()
    ->get('https://your-society.example.com/api/v1/society/resident/dashboard')
    ->throw()
    ->json();
200 OK Sample response
{
  "my_units": [
    {
      "id": 101,
      "unit_number": "A-101",
      "status": "occupied"
    }
  ],
  "open_complaints": 1,
  "open_maintenance": 2,
  "pending_invoices": 45000,
  "upcoming_bookings": 1,
  "announcements": [
    {
      "id": 8,
      "title": "Water maintenance schedule",
      "created_at": "2026-10-18T09:00:00.000000Z"
    }
  ],
  "active_polls": 2
}
File a complaint
curl -X POST "https://your-society.example.com/api/v1/society/resident/complaints" \
  -H "Authorization: Bearer YOUR_SOCIETY_API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "unit_id": 101,
    "subject": "Water leakage in bathroom",
    "description": "Water is leaking from the ceiling near the shower.",
    "priority": "high"
  }'
const response = await fetch("https://your-society.example.com/api/v1/society/resident/complaints", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_SOCIETY_API_TOKEN",
    "Accept": "application/json",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    unit_id: 101,
    subject: "Water leakage in bathroom",
    description: "Water is leaking from the ceiling near the shower.",
    priority: "high"
  })
});

const complaint = await response.json();
use Illuminate\Support\Facades\Http;

$complaint = Http::withToken('YOUR_SOCIETY_API_TOKEN')
    ->acceptJson()
    ->post('https://your-society.example.com/api/v1/society/resident/complaints', [
        'unit_id' => 101,
        'subject' => 'Water leakage in bathroom',
        'description' => 'Water is leaking from the ceiling near the shower.',
        'priority' => 'high',
    ])->throw()->json();
201 Created Sample response
{
  "id": 301
}
GET /society/resident/dashboard — Dashboard — my units, open complaints, pending invoices, announcements
GET /society/resident/profile — Get profile with unit ownerships
PUT /society/resident/profile — Update profile (first_name, last_name, phone)
GET /society/resident/invoices — List my invoices (?status=)
GET /society/resident/invoices/{id} — Invoice detail with items and payments
GET /society/resident/payments — List my payments (?status=)
GET /society/resident/contracts — My tenancy contracts
GET /society/resident/complaints — My complaints (?status=)
GET /society/resident/complaints/{id} — Complaint detail with updates
POST /society/resident/complaints — File a complaint
GET /society/resident/maintenance — My maintenance requests (?status=)
GET /society/resident/maintenance/{id} — Maintenance detail with work orders
POST /society/resident/maintenance — Submit maintenance request
GET /society/resident/facilities — List active facilities
GET /society/resident/facilities/check-availability — Check facility availability (?facility_id=, ?date=)
GET /society/resident/bookings — My bookings (?status=)
POST /society/resident/bookings — Create a booking
POST /society/resident/bookings/{id}/cancel — Cancel a booking
GET /society/resident/polls — List active polls
POST /society/resident/polls/{id}/vote — Vote on a poll
GET /society/resident/announcements — List announcements
GET /society/resident/transfer-requests — My transfer requests (?status=)
POST /society/resident/transfer-requests — Submit transfer request
GET /society/resident/chain-of-title/{unitId} — Chain of title for a unit
GET /society/resident/notifications — List notifications
PUT /society/resident/notifications/{id}/read — Mark notification read
POST /society/resident/change-password — Change password

5. PMF Owner API portal

Property owner portal for PMF. Send a PMF token via Authorization: Bearer YOUR_PMF_API_TOKEN or the X-PMF-Token header. Prefix: /api/v1/pmf/owner

Owner dashboard (selected keys, arrays abbreviated)
curl "https://your-pmf.example.com/api/v1/pmf/owner/dashboard" \
  -H "Authorization: Bearer YOUR_PMF_API_TOKEN" \
  -H "Accept: application/json"
const response = await fetch("https://your-pmf.example.com/api/v1/pmf/owner/dashboard", {
  headers: {
    "Authorization": "Bearer YOUR_PMF_API_TOKEN",
    "Accept": "application/json"
  }
});

const dashboard = await response.json();
use Illuminate\Support\Facades\Http;

$dashboard = Http::withToken('YOUR_PMF_API_TOKEN')
    ->acceptJson()
    ->get('https://your-pmf.example.com/api/v1/pmf/owner/dashboard')
    ->throw()
    ->json();
200 OK Sample response
{
  "portfolio": {
    "total_properties": 6,
    "occupied_count": 5,
    "vacant_count": 1,
    "occupancy_rate": 83.3,
    "total_monthly_rent": 270000
  },
  "financial": {
    "collected_ytd": 2450000,
    "pending_amount": 90000,
    "maintenance_costs_ytd": 185000,
    "open_maintenance": 2,
    "open_complaints": 1,
    "net_operating_income": 2265000
  },
  "health_score": {
    "score": 88,
    "grade": "Good",
    "color": "#10b981",
    "occupancy_score": 83.3,
    "collection_score": 92.5,
    "noi_score": 75,
    "ar_score": 100
  },
  "yoy_comparison": {
    "collected_ytd": 2450000,
    "collected_last_ytd": 2180000,
    "change": 270000,
    "change_pct": 12.4
  },
  "quick_snapshot": {
    "vacancy_cost": 45000,
    "collection_rate": 92.5,
    "open_maintenance": 2,
    "pending_amount": 90000
  },
  "ar_aging": {
    "current": 45000,
    "overdue30": 0,
    "overdue60": 0,
    "overdue90": 0,
    "overdue90plus": 0
  },
  "monthly_trends": {
    "labels": ["Aug 2026", "Sep 2026"],
    "collected": [220000, 270000],
    "maintenance": [12000, 8000],
    "billed": [270000, 270000],
    "noi": [208000, 262000]
  },
  "expense_breakdown": [
    { "type": "Plumbing", "total": 8000 }
  ],
  "lease_pipeline": [
    { "month": "Oct 2026", "count": 1 }
  ],
  "cash_flow_projection": {
    "monthly_rent": 270000,
    "month1": 270000,
    "month2": 270000,
    "month3": 270000,
    "total_income": 810000,
    "estimated_expenses": 25000,
    "net_projected": 785000
  },
  "tenant_payment_behavior": [
    { "unit_number": "A-101", "on_time": 10, "late": 1, "total": 11, "on_time_rate": 90.9 }
  ],
  "property_performance": [
    {
      "unit_number": "A-101",
      "status": "occupied",
      "monthly_rent": 45000,
      "collected_ytd": 495000,
      "maintenance_ytd": 20000,
      "noi_ytd": 475000,
      "tenant_name": "Jane Tenant"
    }
  ],
  "recent_activity": {
    "payments": [],
    "upcoming_expiries": [],
    "recent_messages": [],
    "notifications": [],
    "documents": []
  }
}
GET /pmf/owner/dashboard — Full dashboard — portfolio, financial, health score, AR aging, trends, behaviors
GET /pmf/owner/units — List owned units with tenant + outstanding info
GET /pmf/owner/invoices — List invoices (?status=, ?search=)
GET /pmf/owner/invoices/{id} — Invoice detail with items and payments
GET /pmf/owner/maintenance — List maintenance requests (?status=)
GET /pmf/owner/maintenance/{id} — Maintenance detail with work orders
GET /pmf/owner/payments — List payments (?status=)
GET /pmf/owner/complaints — List my complaints (?status=)
GET /pmf/owner/complaints/{id} — Complaint detail with updates
GET /pmf/owner/messages — Staff-to-owner messages
GET /pmf/owner/notifications — List notifications
GET /pmf/owner/documents — List shared documents
GET /pmf/owner/profile — Get profile with owned units
PUT /pmf/owner/profile — Update profile (first_name, last_name, phone)
POST /pmf/owner/change-password — Change password
GET /pmf/owner/rental-income — Rental income report (?year=) — monthly billed/collected/outstanding
GET /pmf/owner/budget-vs-actual — Budget vs actual comparison (?year=)
GET /pmf/owner/occupancy-report — Occupancy stats — by building, rates
GET /pmf/owner/security-deposits — Security deposit summary
GET /pmf/owner/maintenance-expenses — Maintenance expenses by type/month (?year=)
GET /pmf/owner/rent-roll — Full rent roll — all units with tenant, rent, outstanding
GET /pmf/owner/valuation-history — Valuation history (?unit_id=)
GET /pmf/owner/reports — Reports (?type=summary|income|expense|occupancy, ?from=, ?to=)
GET /pmf/owner/search — Search (?q=) — units, invoices, payments, transactions
GET /pmf/owner/export — Export all data as JSON
GET /pmf/owner/polls — List active polls
GET /pmf/owner/polls/{id} — Poll detail with options
POST /pmf/owner/polls/{id}/vote — Vote on poll
GET /pmf/owner/polls/{id}/results — Poll results

6. PMF Staff API staff

Full PMF administration endpoints. Auth via PMF API token. Prefix: /api/v1/pmf/staffThis is the largest endpoint group covering all PMF modules

PMF staff dashboard
curl "https://your-pmf.example.com/api/v1/pmf/staff/dashboard" \
  -H "Authorization: Bearer YOUR_PMF_API_TOKEN" \
  -H "Accept: application/json"
const response = await fetch("https://your-pmf.example.com/api/v1/pmf/staff/dashboard", {
  headers: {
    "Authorization": "Bearer YOUR_PMF_API_TOKEN",
    "Accept": "application/json"
  }
});

const dashboard = await response.json();
use Illuminate\Support\Facades\Http;

$dashboard = Http::withToken('YOUR_PMF_API_TOKEN')
    ->acceptJson()
    ->get('https://your-pmf.example.com/api/v1/pmf/staff/dashboard')
    ->throw()
    ->json();
200 OK Sample response
{
  "total_units": 150,
  "occupied_units": 118,
  "vacant_units": 32,
  "total_owners": 94,
  "active_tenants": 118,
  "open_complaints": 5,
  "pending_maintenance": 9,
  "pending_invoices": 1240000,
  "collected_ytd": 18600000,
  "pending_bookings": 3,
  "employees": 27,
  "recent_activities": {
    "payments": [],
    "complaints": [],
    "bookings": []
  }
}
Create an invoice with line items
curl -X POST "https://your-pmf.example.com/api/v1/pmf/staff/invoices" \
  -H "Authorization: Bearer YOUR_PMF_API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "unit_id": 86,
    "user_id": 211,
    "invoice_number": "INV-2026-0042",
    "type": "rent",
    "total": 60000,
    "due_amount": 60000,
    "due_date": "2026-11-05",
    "description": "November 2026 rent",
    "items": [
      { "description": "Monthly rent", "amount": 60000, "quantity": 1 }
    ]
  }'
const response = await fetch("https://your-pmf.example.com/api/v1/pmf/staff/invoices", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_PMF_API_TOKEN",
    "Accept": "application/json",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    unit_id: 86,
    user_id: 211,
    invoice_number: "INV-2026-0042",
    type: "rent",
    total: 60000,
    due_amount: 60000,
    due_date: "2026-11-05",
    description: "November 2026 rent",
    items: [
      { description: "Monthly rent", amount: 60000, quantity: 1 }
    ]
  })
});

const invoice = await response.json();
use Illuminate\Support\Facades\Http;

$invoice = Http::withToken('YOUR_PMF_API_TOKEN')
    ->acceptJson()
    ->post('https://your-pmf.example.com/api/v1/pmf/staff/invoices', [
        'unit_id' => 86,
        'user_id' => 211,
        'invoice_number' => 'INV-2026-0042',
        'type' => 'rent',
        'total' => 60000,
        'due_amount' => 60000,
        'due_date' => '2026-11-05',
        'description' => 'November 2026 rent',
        'items' => [
            ['description' => 'Monthly rent', 'amount' => 60000, 'quantity' => 1],
        ],
    ])->throw()->json();
201 Created Sample response
{
  "id": 701
}
Dashboard & Property
GET /pmf/staff/dashboard — Staff dashboard — summary counts, recent activities
GET /pmf/staff/buildings — List buildings
GET /pmf/staff/buildings/{id} — Building detail with floors and unit count
POST /pmf/staff/buildings — Create building
PUT /pmf/staff/buildings/{id} — Update building
DELETE /pmf/staff/buildings/{id} — Delete building
GET /pmf/staff/units — List units (?status=, ?building_id=, ?search=)
GET /pmf/staff/units/{id} — Unit detail — owners, tenant, outstanding, maintenance
POST /pmf/staff/units — Create unit
PUT /pmf/staff/units/{id} — Update unit
GET /pmf/staff/owners — List owners (?search=)
GET /pmf/staff/tenants — List tenants (?status=, ?search=)
Parking & Valuations
GET /pmf/staff/parking/floors — List parking floors (?building_id=)
GET /pmf/staff/parking/slots — List parking slots (?floor_id=, ?status=)
GET /pmf/staff/parking/assignments — List parking assignments (?unit_id=, ?status=)
GET /pmf/staff/valuations — List valuations (?unit_id=)
POST /pmf/staff/valuations — Create valuation
Move In/Out & Contracts
GET /pmf/staff/move-ins — List move-in requests (?status=)
POST /pmf/staff/move-ins — Create move-in request
POST /pmf/staff/move-ins/{id}/approve — Approve move-in
GET /pmf/staff/move-outs — List move-out requests (?status=)
POST /pmf/staff/move-outs — Create move-out request
GET /pmf/staff/contracts — List tenancy contracts (?status=)
GET /pmf/staff/contracts/{id} — Contract detail with invoices
Invoicing, Payments & Accounting
GET /pmf/staff/invoices — List invoices (?status=, ?type=, ?unit_id=, ?search=)
GET /pmf/staff/invoices/{id} — Invoice detail with items and payments
POST /pmf/staff/invoices — Create invoice with line items
PUT /pmf/staff/invoices/{id}/status — Update invoice status
GET /pmf/staff/payments — List payments (?status=, ?method=, ?unit_id=)
GET /pmf/staff/payments/{id} — Payment detail
POST /pmf/staff/payments — Record payment (auto-decrements invoice)
GET /pmf/staff/pdcs — List post-dated cheques (?status=)
POST /pmf/staff/pdcs — Add PDC
PUT /pmf/staff/pdcs/{id}/status — Update PDC status (cleared/bounced)
GET /pmf/staff/management-fees — List management fees (?status=)
POST /pmf/staff/management-fees — Add management fee
GET /pmf/staff/insurance — List insurance policies
POST /pmf/staff/insurance — Add insurance policy
GET /pmf/staff/journal-entries — List journal entries (?type=)
POST /pmf/staff/journal-entries — Create journal entry
GET /pmf/staff/chart-of-accounts — List chart of accounts
HR (Employees, Attendance, Leaves, Advances, Payroll)
GET /pmf/staff/employees — List employees (?status=, ?department_id=, ?search=)
POST /pmf/staff/employees — Create employee
GET /pmf/staff/employees/{id} — Employee detail with attendance, leaves, advances, payroll
GET /pmf/staff/departments — List departments
POST /pmf/staff/departments — Create department
GET /pmf/staff/designations — List designations
POST /pmf/staff/designations — Create designation
GET /pmf/staff/attendance — List attendance (?employee_id=, ?date=, ?status=)
POST /pmf/staff/attendance — Record attendance
GET /pmf/staff/leaves — List leaves (?employee_id=, ?status=)
POST /pmf/staff/leaves — Request leave
PUT /pmf/staff/leaves/{id}/status — Approve/reject leave
GET /pmf/staff/advances — List advances (?employee_id=, ?status=)
POST /pmf/staff/advances — Request advance
PUT /pmf/staff/advances/{id}/status — Update advance status
GET /pmf/staff/payroll — List payroll (?employee_id=, ?month=, ?status=)
POST /pmf/staff/payroll — Process payroll
PUT /pmf/staff/payroll/{id}/status — Update payroll status
Services (Facilities, Bookings, Complaints, Maintenance, Polls, Visitors)
GET /pmf/staff/facilities — List facilities
POST /pmf/staff/facilities — Create facility
GET /pmf/staff/bookings — List bookings (?status=, ?facility_id=)
PUT /pmf/staff/bookings/{id}/status — Approve/reject/cancel booking
GET /pmf/staff/complaints — List complaints (?status=, ?priority=)
GET /pmf/staff/complaints/{id} — Complaint detail with updates
PUT /pmf/staff/complaints/{id}/status — Update complaint status
POST /pmf/staff/complaints/{id}/assign — Assign complaint to staff
GET /pmf/staff/maintenance — List maintenance (?status=, ?type=)
GET /pmf/staff/maintenance/{id} — Maintenance detail with work orders
PUT /pmf/staff/maintenance/{id}/status — Update maintenance status
GET /pmf/staff/polls — List polls (?status=)
POST /pmf/staff/polls — Create poll with options
GET /pmf/staff/polls/{id} — Poll detail with vote count
GET /pmf/staff/visitors — List visitors (?status=)
POST /pmf/staff/visitors — Create visitor entry
PUT /pmf/staff/visitors/{id}/status — Update visitor status
Assets, Vendors, Users & Permissions
GET /pmf/staff/assets — List assets (?status=, ?category=)
POST /pmf/staff/assets — Create asset
GET /pmf/staff/vendors — List vendors
POST /pmf/staff/vendors — Create vendor
GET /pmf/staff/users — List users (?status=, ?role_id=, ?search=)
GET /pmf/staff/roles — List roles
GET /pmf/staff/permissions — List all permissions
CMS (Pages, Menus, Banners, Gallery, Events, Enquiries, Branding)
GET /pmf/staff/cms/pages — List CMS pages
POST /pmf/staff/cms/pages — Create CMS page
GET /pmf/staff/cms/menus — List CMS menus
POST /pmf/staff/cms/menus — Create CMS menu item
GET /pmf/staff/cms/banners — List banners
GET /pmf/staff/cms/gallery — List gallery images
GET /pmf/staff/cms/events — List events (?upcoming=1)
GET /pmf/staff/cms/enquiries — List contact enquiries
GET /pmf/staff/cms/branding — Get branding settings
PUT /pmf/staff/cms/branding — Update branding settings
GET /pmf/staff/cms/settings — All CMS settings by group
Reports, Notifications, Settings & Profile
GET /pmf/staff/reports — Reports (?type=summary|income|occupancy|maintenance|complaints, ?from=, ?to=)
GET /pmf/staff/notifications — List notifications
POST /pmf/staff/notifications — Send notification
PUT /pmf/staff/notifications/{id}/read — Mark notification read
GET /pmf/staff/settings — Get PMF settings
PUT /pmf/staff/settings — Update PMF settings
GET /pmf/staff/profile — Get staff profile
POST /pmf/staff/change-password — Change password

7. PMF Tenant API portal

Tenant portal for PMF societies. Auth via PMF API token. Prefix: /api/v1/pmf/tenant

PMF tenant dashboard
curl "https://your-pmf.example.com/api/v1/pmf/tenant/dashboard" \
  -H "Authorization: Bearer YOUR_PMF_API_TOKEN" \
  -H "Accept: application/json"
const response = await fetch("https://your-pmf.example.com/api/v1/pmf/tenant/dashboard", {
  headers: {
    "Authorization": "Bearer YOUR_PMF_API_TOKEN",
    "Accept": "application/json"
  }
});

const dashboard = await response.json();
use Illuminate\Support\Facades\Http;

$dashboard = Http::withToken('YOUR_PMF_API_TOKEN')
    ->acceptJson()
    ->get('https://your-pmf.example.com/api/v1/pmf/tenant/dashboard')
    ->throw()
    ->json();
200 OK Sample response
{
  "tenant": {
    "first_name": "Jane",
    "last_name": "Tenant",
    "email": "tenant@example.com",
    "phone": "+92 300 1234567"
  },
  "my_units": [
    {
      "id": 86,
      "unit_number": "A-101",
      "building_name": "Tower A",
      "status": "occupied"
    }
  ],
  "current_contract": {
    "id": 44,
    "contract_period_from": "2026-01-01",
    "contract_period_to": "2026-12-31",
    "rent_amount": 60000,
    "unit_number": "A-101"
  },
  "pending_invoices": 60000,
  "overdue_count": 0,
  "open_complaints": 0,
  "pending_maintenance": 1,
  "upcoming_bookings": 1,
  "unread_notifications": 3,
  "announcements": []
}
Submit a maintenance request
curl -X POST "https://your-pmf.example.com/api/v1/pmf/tenant/maintenance" \
  -H "Authorization: Bearer YOUR_PMF_API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "unit_id": 86,
    "type": "plumbing",
    "title": "Kitchen sink blockage",
    "description": "The kitchen sink drains very slowly.",
    "priority": "high"
  }'
const response = await fetch("https://your-pmf.example.com/api/v1/pmf/tenant/maintenance", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_PMF_API_TOKEN",
    "Accept": "application/json",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    unit_id: 86,
    type: "plumbing",
    title: "Kitchen sink blockage",
    description: "The kitchen sink drains very slowly.",
    priority: "high"
  })
});

const request = await response.json();
use Illuminate\Support\Facades\Http;

$request = Http::withToken('YOUR_PMF_API_TOKEN')
    ->acceptJson()
    ->post('https://your-pmf.example.com/api/v1/pmf/tenant/maintenance', [
        'unit_id' => 86,
        'type' => 'plumbing',
        'title' => 'Kitchen sink blockage',
        'description' => 'The kitchen sink drains very slowly.',
        'priority' => 'high',
    ])->throw()->json();
201 Created Sample response
{
  "id": 812
}
GET /pmf/tenant/dashboard — Dashboard — my units, contract, pending invoices, notifications, announcements
GET /pmf/tenant/profile — Profile with current tenancy details
PUT /pmf/tenant/profile — Update profile (first_name, last_name, phone)
GET /pmf/tenant/tenancy — Active tenancy contract with unit details
GET /pmf/tenant/invoices — My invoices (?status=)
GET /pmf/tenant/invoices/{id} — Invoice detail with items and payments
GET /pmf/tenant/payments — My payment history (?status=)
GET /pmf/tenant/complaints — My complaints (?status=)
GET /pmf/tenant/complaints/{id} — Complaint detail with updates
POST /pmf/tenant/complaints — File a complaint
GET /pmf/tenant/maintenance — My maintenance requests (?status=)
GET /pmf/tenant/maintenance/{id} — Maintenance detail with work orders
POST /pmf/tenant/maintenance — Submit maintenance request
GET /pmf/tenant/facilities — List active facilities
GET /pmf/tenant/bookings — My bookings (?status=)
POST /pmf/tenant/bookings — Create a booking
POST /pmf/tenant/bookings/{id}/cancel — Cancel a booking
GET /pmf/tenant/documents — View shared documents
GET /pmf/tenant/notifications — List notifications
PUT /pmf/tenant/notifications/{id}/read — Mark notification read
GET /pmf/tenant/messages — Staff-to-tenant messages
POST /pmf/tenant/messages — Send message to staff
GET /pmf/tenant/polls — List active polls
POST /pmf/tenant/polls/{id}/vote — Vote on a poll
POST /pmf/tenant/change-password — Change password

Response formats

All responses are JSON. Most list endpoints use Laravel's paginator envelope and accept ?page= and usually ?per_page= (capped at 100). Some endpoints, such as /society/units and /society/residents, return a flat JSON array — check the endpoint description.

Paginated collection
{
  "current_page": 1,
  "data": [
    { "id": 101, "unit_number": "A-101" }
  ],
  "first_page_url": "https://your-society.example.com/api/v1/society/staff/units?page=1",
  "from": 1,
  "last_page": 3,
  "last_page_url": "https://your-society.example.com/api/v1/society/staff/units?page=3",
  "next_page_url": "https://your-society.example.com/api/v1/society/staff/units?page=2",
  "path": "https://your-society.example.com/api/v1/society/staff/units",
  "per_page": 20,
  "prev_page_url": null,
  "to": 20,
  "total": 58
}
Single resource / mutation
{
  "id": 101,
  "unit_number": "A-101",
  "status": "occupied"
}

// 201 Created
{
  "id": 501
}

// Status change
{
  "message": "Status updated"
}

Monetary values are numeric (not formatted strings). Dates use ISO 8601. Resource IDs are integers. Some PMF endpoints return {"data": [...]} instead of a paginator when the result is intentionally unpaginated.

8. Error Responses

Central and society endpoints use a message key. PMF resource controllers commonly use an error key. Validation failures always return 422 with an errors object.

{ "message": "Invalid credentials" } // 400 or 401 { "error": "Unauthorized" } // 401 (PMF routes) { "message": "Account inactive" } // 403 { "error": "Not found" } // 404 (PMF routes) { "message": "The given data was invalid.", "errors": { "email": [ "The email field is required." ] } } // 422 { "error": "Already voted" } // 422 (polls) { "message": "Too Many Attempts." } // 429 — see Retry-After and X-RateLimit-* headers
StatusMeaning
400Missing tenant context or invalid PMF code.
401Missing, invalid, revoked, or expired token.
403Inactive account, persona mismatch, or insufficient permission.
404Resource not found or not owned by the authenticated user.
422Validation failed; inspect the errors object.
429Rate limit exceeded; back off and retry after the interval.

9. Rate Limits & Headers

HeaderDescription
AuthorizationBearer YOUR_TOKEN. Required for society and PMF endpoints.
X-PMF-TokenAlternative to Authorization for PMF endpoints only.
Content-Typeapplication/json for requests with a body.
Acceptapplication/json.
X-RateLimit-LimitMaximum requests allowed in the current window.
X-RateLimit-RemainingRequests remaining in the current window.
Retry-AfterSeconds to wait after a 429 response.

General API limit: 60 requests per minute per authenticated user (or per IP for public endpoints). Authentication endpoints are stricter: 10 requests per minute per IP for login, and 5 requests per minute per IP for registration and password reset.

Ready to build

Start your free trial and get API access immediately. Create PMF API tokens from the admin panel

Start Free Trial