https://nyumbahub.maketech.co.tz/api/
https://nyumbahub.maketech.co.tz/api/test
All requests and responses use
application/json. Always set the header:
Content-Type: application/json
All endpoints except
POST /auth/login/
require a valid JWT access token sent in the Authorization
header:
Authorization: Bearer <access_token>
// 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
"admin" → Full access — manage houses, tenants, payments, users, reports "caretaker" → Read access + can record payments. Cannot delete or manage users.
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
{
"room": ["This room already has an active tenant."],
"password": ["Passwords do not match."]
}
{
"detail": "Given token not valid for any token type",
"code": "token_not_valid"
}
-- 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
{
"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 */ ]
}
{
"username": "admin",
"password": "yourpassword"
}
{
"access": "eyJhbGciOiJIUzI1NiIs...",
"refresh": "eyJhbGciOiJIUzI1NiIs...",
"user": {
"id": 1,
"username": "admin",
"email": "admin@nyumbahub.com",
"role": "admin",
"phone": "+255712345678"
}
}
{ "refresh": "your-refresh-token" }
{ "detail": "Logged out successfully." }
{ "refresh": "your-refresh-token" }
{ "access": "new-access-token..." }
{
"id": 1,
"username": "admin",
"email": "admin@nyumbahub.com",
"role": "admin",
"phone": "+255712345678",
"is_active": true,
"date_joined": "2025-01-15T08:00:00Z"
}
{
"old_password": "currentpass",
"new_password": "newstrongpass123"
}
{ "detail": "Password changed successfully." }
[
{ "id": 1, "username": "admin", "role": "admin", "is_active": true },
{ "id": 2, "username": "caretaker1", "role": "caretaker", "is_active": true }
]
{
"username": "caretaker2", /* required */
"email": "care@email.com", /* required */
"password": "pass123!", /* required */
"password2": "pass123!", /* required — must match */
"role": "caretaker", /* "admin" | "caretaker" */
"phone": "+255700000000" /* optional */
}
| Param | Type | Description |
|---|---|---|
| search | string | Search by name or address |
| ordering | string | name, created_at (prefix - for desc) |
[
{
"id": 1,
"name": "House Msasani",
"address": "Msasani, Dar es Salaam",
"total_rooms": 8,
"occupied_rooms": 6,
"vacant_rooms": 2,
"occupancy_rate": 75.0
}
]
{
"name": "House Kinondoni", /* required */
"address": "Kinondoni, DSM", /* required */
"description": "2 storey house", /* optional */
"image": <file upload> /* optional — FormData */
}
{
"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" }
]
}
| Param | Example | Description |
|---|---|---|
| status | vacant | vacant | occupied | maintenance |
| house | 2 | Filter by house ID |
| floor | 1 | Filter by floor number |
GET /api/properties/rooms/?status=vacant&house=2
{
"house": 1, /* required — house ID */
"room_number": "101", /* required */
"monthly_rent": "150000.00", /* required — TZS */
"floor": 1, /* optional, default 1 */
"description": "Corner room" /* optional */
}
Use this to mark a room as under maintenance or vacant without editing the full record.
{
"status": "maintenance"
/* "vacant" | "occupied" | "maintenance" */
}
Returns occupancy data for all 8 houses + portfolio totals. Great for the dashboard overview cards.
{
"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
}
}
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.
| Param | Example | Description |
|---|---|---|
| status | active | active | former |
| room__house | 2 | Filter by house ID |
| search | juma | Searches name, phone, email, national ID |
[
{
"id": 1,
"full_name": "Juma Mwangi",
"phone": "+255712345678",
"room_number": "101",
"house_name": "House Msasani",
"move_in_date": "2025-01-01",
"status": "active"
}
]
{
"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 */
}
{
"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"
}
{
"status": "former",
"move_out_date": "2025-06-30"
}
// Room is automatically set to "vacant" on the backend
[
{
"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"
}
]
{
"name": "Bank Transfer", /* required */
"description": "Direct bank deposit", /* optional */
"is_active": true /* optional — default true */
}
{
"id": 3,
"name": "Bank Transfer",
"description": "Direct bank deposit",
"is_active": true,
"created_at": "2026-05-31T10:00:00Z"
}
{
"is_active": false /* deactivate payment method */
}
Cannot delete if payments exist with this method. Deactivate instead.
The
status
field is computed automatically:
amount_paid == amount_due → "paid" | amount_paid >
0 → "partial" | amount_paid == 0 → "unpaid"
{
"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 */
}
{
"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"
}
| Param | Default | Description |
|---|---|---|
| month | current | 1–12 |
| year | current | e.g. 2025 |
{
"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 */ ]
}
Useful for the "who hasn't paid" screen. Accepts same
month
and
year
params as the summary endpoint.
GET /api/payments/unpaid/?month=6&year=2025
[
{
"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"
}
]
{
"name": "Staff / Caretaker", /* required */
"description": "Caretaker salary and wages", /* optional */
"is_active": true /* optional — default true */
}
{
"id": 5,
"name": "Staff / Caretaker",
"description": "Caretaker salary and wages",
"is_active": true,
"created_at": "2026-05-31T10:00:00Z"
}
{
"is_active": false /* deactivate category */
}
Cannot delete if expenses exist with this category. Deactivate instead.
{
"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 */
}
| 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 |
Call this on app load to populate all dashboard cards. Returns occupancy, tenant count, and this month's financial snapshot.
{
"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
}
}
Returns all 12 months for the given year. Feed this directly into your bar chart.
GET /api/reports/monthly/?year=2025
{
"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 /api/reports/by-house/?year=2025
{
"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
}
]
}
[
{ "year": 2024, "income": 65000000, "expenses": 4100000, "net": 60900000 },
{ "year": 2025, "income": 72000000, "expenses": 4200000, "net": 67800000 }
]
GET /api/reports/expenses/by-category/?year=2025&month=6
[
{ "category": "maintenance", "total": 240000, "count": 4 },
{ "category": "utilities", "total": 80000, "count": 2 },
{ "category": "staff", "total": 30000, "count": 1 }
]
Returns all tenants — current and former — who have ever lived in this room, ordered by most recent first.
"vacant" → Room is empty and available "occupied" → Room has an active tenant "maintenance" → Room is being repaired / not rentable
"active" → Currently living in the room "former" → Has moved out
"cash" → Cash payment "mpesa" → M-Pesa mobile money "bank" → Bank transfer
"paid" → amount_paid >= amount_due "partial" → 0 < amount_paid < amount_due "unpaid" → amount_paid == 0
"maintenance" → Repairs, fixes "utilities" → Water, electricity "staff" → Caretaker wages "taxes" → Government fees "other" → Anything else
"admin" → Full system access "caretaker" → Read + record payments only