NyumbaHub REST API — Developer Reference
v1.0 · Django REST Framework · JWT Auth
🏠 NyumbaHub API Reference

Time to Cook
Frontend.

Complete REST API for the NyumbaHub rental property management system. All endpoints, request bodies, response shapes, and example code your React app needs.

40+
Endpoints
7
Modules
JWT
Auth method
JSON
Format
📖
Introduction
Everything you need to know before you start
Base URL
https://nyumbahub.maketech.co.tz/api/
Test saver
https://nyumbahub.maketech.co.tz/api/test
Content Type

All requests and responses use application/json. Always set the header:

Content-Type: application/json

⚠ Authentication Required

All endpoints except POST /auth/login/ require a valid JWT access token sent in the Authorization header:

Authorization: Bearer <access_token>

🔐
Authentication Flow
How JWT tokens work in this API
01
POST /auth/login/
Send credentials
02
Get tokens
access + refresh
03
Add to headers
Bearer <token>
04
Token expires
after 8 hours
05
POST /token/refresh/
Get new access token
Token Lifetimes
// Access token — used for all API calls
access_token:  8 hours

// Refresh token — used only to get a new access token
refresh_token: 7 days
Roles
"admin"      → Full access — manage houses, tenants, payments, users, reports
"caretaker"  → Read access + can record payments. Cannot delete or manage users.
Errors & Responses
Standard HTTP status codes this API returns
200 OK           — Request succeeded
201 Created      — Resource created successfully
400 Bad Request  — Validation failed (see errors object)
401 Unauthorized — Token missing or expired
403 Forbidden    — Authenticated but not allowed (role issue)
404 Not Found    — Resource does not exist
405 Method Not Allowed
500 Server Error — Something broke on the backend
Validation Error Shape (400)
{
  "room": ["This room already has an active tenant."],
  "password": ["Passwords do not match."]
}
Auth Error Shape (401)
{
  "detail": "Given token not valid for any token type",
  "code": "token_not_valid"
}
🔍
Filtering, Search & Pagination
Query parameters available on list endpoints
-- Filter by exact field value
GET /api/properties/rooms/?status=vacant
GET /api/payments/?payment_month=6&payment_year=2025
GET /api/tenants/?status=active&room__house=2

-- Search (partial text match)
GET /api/tenants/?search=john
GET /api/properties/rooms/?search=101

-- Sort results
GET /api/tenants/?ordering=-move_in_date     (- = descending)
GET /api/payments/?ordering=amount_due

-- Pagination (20 per page by default)
GET /api/tenants/?page=2
Paginated Response Shape
{
  "count": 47,
  "next": "https://nyumbahub.maketech.co.tz/api/tenants/?page=3",
  "previous": "https://nyumbahub.maketech.co.tz/api/tenants/?page=1",
  "results": [ /* array of objects */ ]
}
🔐
Auth & Users
Login, token management, user profiles
POST /auth/login/ Login and get tokens
public — no token needed
Request Body
{
  "username": "admin",
  "password": "yourpassword"
}
Response 200
{
  "access": "eyJhbGciOiJIUzI1NiIs...",
  "refresh": "eyJhbGciOiJIUzI1NiIs...",
  "user": {
    "id": 1,
    "username": "admin",
    "email": "admin@nyumbahub.com",
    "role": "admin",
    "phone": "+255712345678"
  }
}
POST /auth/logout/ Logout and blacklist token
auth required
Request Body
{ "refresh": "your-refresh-token" }
Response 200
{ "detail": "Logged out successfully." }
POST /auth/token/refresh/ Get new access token
Request Body
{ "refresh": "your-refresh-token" }
Response 200
{ "access": "new-access-token..." }
GET /auth/me/ Get current user profile
auth required
Response 200
{
  "id": 1,
  "username": "admin",
  "email": "admin@nyumbahub.com",
  "role": "admin",
  "phone": "+255712345678",
  "is_active": true,
  "date_joined": "2025-01-15T08:00:00Z"
}
POST /auth/change-password/ Change own password
auth required
Request Body
{
  "old_password": "currentpass",
  "new_password": "newstrongpass123"
}
Response 200
{ "detail": "Password changed successfully." }
GET /auth/users/ List all system users
admin only
Response 200
[
  { "id": 1, "username": "admin", "role": "admin", "is_active": true },
  { "id": 2, "username": "caretaker1", "role": "caretaker", "is_active": true }
]
POST /auth/users/ Create new user account
admin only
Request Body
{
  "username": "caretaker2",   /* required */
  "email": "care@email.com",  /* required */
  "password": "pass123!",      /* required */
  "password2": "pass123!",     /* required — must match */
  "role": "caretaker",        /* "admin" | "caretaker" */
  "phone": "+255700000000"    /* optional */
}
🏠
Properties — Houses & Rooms
Manage the 8 houses and all their rooms
GET /properties/houses/ List all houses with stats
Query Params
Param Type Description
search string Search by name or address
ordering string name, created_at (prefix - for desc)
Response 200
[
  {
    "id": 1,
    "name": "House Msasani",
    "address": "Msasani, Dar es Salaam",
    "total_rooms": 8,
    "occupied_rooms": 6,
    "vacant_rooms": 2,
    "occupancy_rate": 75.0
  }
]
POST /properties/houses/ Add a new house
admin only
Request Body
{
  "name": "House Kinondoni",      /* required */
  "address": "Kinondoni, DSM",     /* required */
  "description": "2 storey house",  /* optional */
  "image": <file upload>           /* optional — FormData */
}
GET /properties/houses/{id}/ Get house + all rooms
Response 200
{
  "id": 1,
  "name": "House Msasani",
  "address": "Msasani, Dar es Salaam",
  "total_rooms": 8,
  "occupancy_rate": 75.0,
  "rooms": [
    { "id": 1, "room_number": "101", "monthly_rent": "150000.00", "status": "occupied" },
    { "id": 2, "room_number": "102", "monthly_rent": "150000.00", "status": "vacant" }
  ]
}
GET /properties/rooms/ List all rooms across all houses
Filter Params
Param Example Description
status vacant vacant | occupied | maintenance
house 2 Filter by house ID
floor 1 Filter by floor number
Example
GET /api/properties/rooms/?status=vacant&house=2
POST /properties/rooms/ Add a room to a house
admin only
Request Body
{
  "house": 1,                  /* required — house ID */
  "room_number": "101",         /* required */
  "monthly_rent": "150000.00",  /* required — TZS */
  "floor": 1,                   /* optional, default 1 */
  "description": "Corner room"  /* optional */
}
PATCH /properties/rooms/{id}/status/ Quick room status update

Use this to mark a room as under maintenance or vacant without editing the full record.

Request Body
{
  "status": "maintenance"
  /* "vacant" | "occupied" | "maintenance" */
}
GET /properties/occupancy/ Full occupancy overview — dashboard

Returns occupancy data for all 8 houses + portfolio totals. Great for the dashboard overview cards.

Response 200
{
  "houses": [
    {
      "id": 1, "name": "House Msasani",
      "total_rooms": 8, "occupied": 6, "vacant": 1,
      "maintenance": 1, "occupancy_rate": 75.0
    }
  ],
  "totals": {
    "total_rooms": 54, "occupied": 42,
    "vacant": 9, "maintenance": 3,
    "occupancy_rate": 77.8
  }
}
👤
Tenants
Register, update, and manage tenant records
⚡ Auto-behaviour

Creating a tenant with a room → room auto-set to occupied.
Setting tenant status to former → room auto-set to vacant.
You cannot assign a room that already has an active tenant.

GET /tenants/ List all tenants
Filter Params
Param Example Description
status active active | former
room__house 2 Filter by house ID
search juma Searches name, phone, email, national ID
Response 200
[
  {
    "id": 1,
    "full_name": "Juma Mwangi",
    "phone": "+255712345678",
    "room_number": "101",
    "house_name": "House Msasani",
    "move_in_date": "2025-01-01",
    "status": "active"
  }
]
POST /tenants/ Register a new tenant
admin only
Request Body
{
  "first_name": "Juma",           /* required */
  "last_name": "Mwangi",          /* required */
  "phone": "+255712345678",      /* required */
  "room": 3,                     /* required — room ID */
  "move_in_date": "2025-06-01", /* required — YYYY-MM-DD */
  "email": "juma@email.com",     /* optional */
  "national_id": "19900101-xxxxx",/* optional */
  "emergency_contact_name": "Ali", /* optional */
  "emergency_contact_phone": "...",/* optional */
  "notes": "Paid deposit"        /* optional */
}
GET /tenants/{id}/ Get full tenant profile
Response 200
{
  "id": 1,
  "full_name": "Juma Mwangi",
  "phone": "+255712345678",
  "email": "juma@email.com",
  "national_id": "19900101-xxxxx",
  "room": 3,
  "room_number": "101",
  "house_name": "House Msasani",
  "house_id": 1,
  "monthly_rent": "150000.00",
  "move_in_date": "2025-01-01",
  "move_out_date": null,
  "status": "active",
  "notes": "Paid deposit"
}
PATCH /tenants/{id}/ Update tenant / mark as moved out
Move out a tenant
{
  "status": "former",
  "move_out_date": "2025-06-30"
}
// Room is automatically set to "vacant" on the backend
💳
Payment Methods
Manage flexible payment method options (Cash, M-Pesa, Bank, etc.)
GET /payments/methods/ List all payment methods
Response 200
[
  {
    "id": 1,
    "name": "Cash",
    "description": "Direct cash payment",
    "is_active": true,
    "created_at": "2026-05-31T10:00:00Z"
  },
  {
    "id": 2,
    "name": "M-Pesa",
    "description": "Mobile money transfer",
    "is_active": true,
    "created_at": "2026-05-31T10:00:00Z"
  }
]
POST /payments/methods/ Create new payment method
admin only
Request Body
{
  "name": "Bank Transfer",        /* required */
  "description": "Direct bank deposit", /* optional */
  "is_active": true                      /* optional — default true */
}
Response 201
{
  "id": 3,
  "name": "Bank Transfer",
  "description": "Direct bank deposit",
  "is_active": true,
  "created_at": "2026-05-31T10:00:00Z"
}
PUT /payments/methods/{id}/ Update payment method
admin only
Request Body
{
  "is_active": false  /* deactivate payment method */
}
DELETE /payments/methods/{id}/ Delete payment method
admin only

Cannot delete if payments exist with this method. Deactivate instead.

💰
Payments
Record and track monthly rent payments
⚡ Auto-status

The status field is computed automatically:
amount_paid == amount_due → "paid"  |  amount_paid > 0 → "partial"  |  amount_paid == 0 → "unpaid"

POST /payments/ Record a rent payment
Request Body
{
  "tenant": 1,                  /* required — tenant ID */
  "amount_due": "150000.00",    /* required — TZS */
  "amount_paid": "150000.00",   /* required */
  "payment_month": 6,            /* required — 1-12 */
  "payment_year": 2025,          /* required */
  "payment_date": "2025-06-03", /* optional */
  "method": 1,                  /* required — PaymentMethod ID */
  "mpesa_reference": "QAB12345",/* optional */
  "notes": ""                   /* optional */
}
Response 201
{
  "id": 42,
  "tenant_name": "Juma Mwangi",
  "amount_due": "150000.00",
  "amount_paid": "150000.00",
  "balance": "0.00",
  "status": "paid",
  "method": 1,
  "method_name": "M-Pesa",
  "mpesa_reference": "QAB12345"
}
GET /payments/summary/ Monthly summary — totals + all records
Query Params
Param Default Description
month current 1–12
year current e.g. 2025
Response 200
{
  "month": 6, "year": 2025,
  "total_expected": "7500000.00",
  "total_collected": "6300000.00",
  "total_outstanding": 1200000.0,
  "paid_count": 36,
  "partial_count": 4,
  "unpaid_count": 8,
  "payments": [ /* full payment objects */ ]
}
GET /payments/unpaid/ Tenants with unpaid or partial rent

Useful for the "who hasn't paid" screen. Accepts same month and year params as the summary endpoint.

Example
GET /api/payments/unpaid/?month=6&year=2025
📂
Expense Categories
Manage expense categories (Maintenance, Utilities, Staff, etc.)
GET /payments/expense/categories/ List all expense categories
Response 200
[
  {
    "id": 1,
    "name": "Maintenance & Repairs",
    "description": "Building repairs and maintenance costs",
    "is_active": true,
    "created_at": "2026-05-31T10:00:00Z"
  },
  {
    "id": 2,
    "name": "Utilities",
    "description": "Water, electricity, and internet",
    "is_active": true,
    "created_at": "2026-05-31T10:00:00Z"
  }
]
POST /payments/expense/categories/ Create new expense category
admin only
Request Body
{
  "name": "Staff / Caretaker",          /* required */
  "description": "Caretaker salary and wages", /* optional */
  "is_active": true                            /* optional — default true */
}
Response 201
{
  "id": 5,
  "name": "Staff / Caretaker",
  "description": "Caretaker salary and wages",
  "is_active": true,
  "created_at": "2026-05-31T10:00:00Z"
}
PUT /payments/expense/categories/{id}/ Update expense category
admin only
Request Body
{
  "is_active": false  /* deactivate category */
}
DELETE /payments/expense/categories/{id}/ Delete expense category
admin only

Cannot delete if expenses exist with this category. Deactivate instead.

📋
Expenses
Track maintenance, utilities, and other costs
POST /payments/expenses/ Record an expense
Request Body
{
  "house": 1,                         /* optional — null = portfolio-wide */
  "category": 1,                         /* required — ExpenseCategory ID */
  "description": "Roof repair room 3", /* required */
  "amount": "85000.00",               /* required — TZS */
  "date": "2025-06-10"               /* required — YYYY-MM-DD */
}
GET /payments/expenses/ List expenses with filters
Filter Params
Param Example Description
category 1 Filter by category ID
house 2 Filter by house ID
year 2025 Filter by year
month 6 Filter by month
📊
Reports
Financial summaries, charts data, and rental history
GET /reports/dashboard/ Main dashboard — load first

Call this on app load to populate all dashboard cards. Returns occupancy, tenant count, and this month's financial snapshot.

Response 200
{
  "today": "2025-06-10",
  "properties": {
    "total_rooms": 54, "occupied": 42,
    "vacant": 9, "maintenance": 3,
    "occupancy_rate": 77.8,
    "total_houses": 8
  },
  "tenants": { "active": 42 },
  "this_month": {
    "month": 6, "year": 2025,
    "rent_expected": 7500000.0,
    "rent_collected": 6300000.0,
    "rent_outstanding": 1200000.0,
    "paid_tenants": 36,
    "unpaid_tenants": 6,
    "expenses": 350000.0,
    "net_income": 5950000.0
  }
}
GET /reports/monthly/ Income vs expenses per month

Returns all 12 months for the given year. Feed this directly into your bar chart.

Example
GET /api/reports/monthly/?year=2025
Response 200
{
  "year": 2025,
  "months": [
    { "month": 1, "income": 5800000, "expenses": 420000, "net": 5380000 },
    { "month": 2, "income": 6100000, "expenses": 310000, "net": 5790000 }
    /* ... months 3-12 */
  ],
  "totals": { "income": 72000000, "expenses": 4200000, "net": 67800000 }
}
GET /reports/by-house/ Per-house income and occupancy
Example
GET /api/reports/by-house/?year=2025
Response 200
{
  "year": 2025,
  "houses": [
    {
      "house_id": 1, "house_name": "House Msasani",
      "total_rooms": 8, "occupied_rooms": 6,
      "occupancy_rate": 75.0,
      "income": 10800000, "expenses": 500000, "net": 10300000
    }
  ]
}
GET /reports/yearly/ Year-by-year financial summary
Response 200
[
  { "year": 2024, "income": 65000000, "expenses": 4100000, "net": 60900000 },
  { "year": 2025, "income": 72000000, "expenses": 4200000, "net": 67800000 }
]
GET /reports/expenses/by-category/ Expense breakdown for pie chart
Example
GET /api/reports/expenses/by-category/?year=2025&month=6
Response 200
[
  { "category": "maintenance", "total": 240000, "count": 4 },
  { "category": "utilities",   "total": 80000,  "count": 2 },
  { "category": "staff",       "total": 30000,  "count": 1 }
]
GET /reports/room/{room_id}/history/ Full rental history for a room

Returns all tenants — current and former — who have ever lived in this room, ordered by most recent first.

🗂
Data Models Reference
Field types and allowed values
Room — status values
"vacant"      → Room is empty and available
"occupied"    → Room has an active tenant
"maintenance" → Room is being repaired / not rentable
Tenant — status values
"active"  → Currently living in the room
"former"  → Has moved out
Payment — method values
"cash"   → Cash payment
"mpesa"  → M-Pesa mobile money
"bank"   → Bank transfer
Payment — status (auto-computed, do not set manually)
"paid"    → amount_paid >= amount_due
"partial" → 0 < amount_paid < amount_due
"unpaid"  → amount_paid == 0
Expense — category values
"maintenance" → Repairs, fixes
"utilities"   → Water, electricity
"staff"       → Caretaker wages
"taxes"       → Government fees
"other"       → Anything else
User — role values
"admin"      → Full system access
"caretaker"  → Read + record payments only