SmartSpend API Documentation

PHP + MySQL backend for the SmartSpend family expense app. Every endpoint below shows exactly what you send and what you get back.

Running on LIVE SERVER - connected to database "czsoft_smartspend". The API is ready to use.

Setup and base URL

This copy is running on the live server, so there is only one address. It works from the browser, from Postman, from the emulator and from a real phone anywhere in the world.

Calling fromBase URL
Everywhere https://www.smartspend.creamerzsoft.com/
Put this in the Android app https://www.smartspend.creamerzsoft.com/api/

Test accounts

RoleEmailPassword
Adminadmin@smartspend.comadmin123
Memberayesha@smartspend.commember123
Memberhamza@smartspend.commember123
Membersara@smartspend.commember123

Sending the token

Call Login first. It returns a token. Save it in the app, then send it as a header on every endpoint marked Login required or Admin only:

Authorization: Bearer d449b6b99cefda157444058487d862d6...

While testing you can also put it in the URL instead: ?token=d449b6b9...

Every response has the same shape

So in the app you always check success first, show message to the user, and read the real content from data.

When it works
{
    "success": true,
    "message": "Login successful",
    "data": {
        "...": "..."
    }
}
When it fails
{
    "success": false,
    "message": "Incorrect email or password",
    "errors": {
        "email": "Invalid email"
    }
}

Status codes used

CodeMeaning
200OK
201Created - signup, add expense, add member
400Bad request
401Not logged in, or wrong password
403Logged in, but not allowed (not the admin)
404Not found
405Wrong method - you sent GET to a POST endpoint
409Email already registered
422Validation failed - check the errors object
500Server or database error

Authentication

Start here. Login gives you a token - every other endpoint needs it.

POST Sign up No login needed
api/auth/signup.php

Creates a NEW household and makes this person its ADMIN. Use this on the Register screen. Other family members are added later by the admin using Add Member.

What you send
Headers
Content-Type: application/json
Fields in the JSON body
FieldTypeRequiredWhat it means
name string required Full name of the person
email string required Must be a valid email and not already registered
password string required At least 6 characters
householdName string optional Family name. If you leave it out we use "<name>'s Family"
monthlyBudget number optional Budget for the whole family. Default 0
Example request body
{
    "name": "Abdul Rehman",
    "email": "abdul@example.com",
    "password": "secret123",
    "householdName": "Khan Family",
    "monthlyBudget": 150000
}
What you get back
HTTP 201  ·  Content-Type: application/json
{
    "success": true,
    "message": "Account created successfully",
    "data": {
        "token": "7f3a9c2e1b4d...64 characters",
        "user": {
            "id": 1,
            "householdId": 1,
            "name": "Abdul Rehman",
            "email": "abdul@example.com",
            "role": "ADMIN",
            "assignedBudget": 150000
        }
    }
}
If something goes wrong
Codemessage you get back
422 Please fill all required fields
422 Please enter a valid email address
422 Password must be at least 6 characters
422 A household can only have one admin, and it is the person who created it
409 This email is already registered
POST Login No login needed
api/auth/login.php

Checks the email and password. Save the token you get back - you must send it on every protected request.

What you send
Headers
Content-Type: application/json
Fields in the JSON body
FieldTypeRequiredWhat it means
email string required The registered email address
password string required The account password
Example request body
{
    "email": "admin@smartspend.com",
    "password": "admin123"
}
What you get back
HTTP 200  ·  Content-Type: application/json
{
    "success": true,
    "message": "Login successful",
    "data": {
        "token": "d449b6b99cefda157444058487d862d6...",
        "user": {
            "id": 1,
            "householdId": 1,
            "name": "Abdul Rehman",
            "email": "admin@smartspend.com",
            "role": "ADMIN",
            "assignedBudget": 50000
        }
    }
}
If something goes wrong
Codemessage you get back
401 Incorrect email or password
422 Please fill all required fields
GET Who am I Login required
api/auth/me.php

Call this when the app opens to check the saved token is still valid. Also fills the profile screen.

What you send
Headers
Authorization: Bearer <your token>
Fields

Nothing to send - just the headers above.

What you get back
HTTP 200  ·  Content-Type: application/json
{
    "success": true,
    "message": "User loaded",
    "data": {
        "user": {
            "id": 1,
            "householdId": 1,
            "name": "Abdul Rehman",
            "email": "admin@smartspend.com",
            "role": "ADMIN",
            "assignedBudget": 50000
        },
        "householdName": "Khan Family",
        "totalSpent": 27700,
        "remaining": 22300
    }
}
If something goes wrong
Codemessage you get back
401 Please login first (missing or expired token)
POST Forgot password No login needed
api/auth/forgot_password.php

Creates a 6-digit code that works for 15 minutes. While DEBUG_MODE is true the code comes back in the response so you can test without an email server. The message is always the same whether the email exists or not, so nobody can use this to find out which emails are registered.

What you send
Headers
Content-Type: application/json
Fields in the JSON body
FieldTypeRequiredWhat it means
email string required The email to send the reset code to
Example request body
{
    "email": "admin@smartspend.com"
}
What you get back
HTTP 200  ·  Content-Type: application/json
{
    "success": true,
    "message": "If that email is registered, a reset code has been sent",
    "data": {
        "code": "874545",
        "note": "Shown only because DEBUG_MODE is true"
    }
}
If something goes wrong
Codemessage you get back
422 Please enter a valid email address
POST Reset password No login needed
api/auth/reset_password.php

Sets a new password using the 6-digit code. The code can only be used once. All old tokens are deleted, so the user must login again.

What you send
Headers
Content-Type: application/json
Fields in the JSON body
FieldTypeRequiredWhat it means
email string required Same email used in Forgot password
code string required The 6-digit code
newPassword string required At least 6 characters
Example request body
{
    "email": "admin@smartspend.com",
    "code": "874545",
    "newPassword": "newpass123"
}
What you get back
HTTP 200  ·  Content-Type: application/json
{
    "success": true,
    "message": "Password changed successfully. Please login again.",
    "data": null
}
If something goes wrong
Codemessage you get back
400 This code is wrong or has expired
422 Password must be at least 6 characters
POST Send a test email Login required
api/auth/test_email.php

Sends a test message to the address on YOUR OWN account and reports exactly what the mail server said. Use this when email "is not working" - forgot_password.php has to stay vague on purpose, this one does not. It always mails the logged-in account's own address, never one you pass in, so it cannot be used to send mail to strangers. To test a different inbox, change your profile email first. Nothing is saved and no reset code is created.

What you send
Headers
Authorization: Bearer <your token>
Content-Type: application/json
Fields

Nothing to send - just the headers above.

What you get back
HTTP 200  ·  Content-Type: application/json
{
    "success": true,
    "message": "Test email sent to you@example.com",
    "data": {
        "sentTo": "you@example.com",
        "tookSeconds": 3.62999999999999989341858963598497211933135986328125,
        "smtp": {
            "host": "smtp.gmail.com",
            "port": 587,
            "username": "smartspend82@gmail.com",
            "encryption": "tls",
            "fromName": "SmartSpend",
            "fromAddress": "smartspend82@gmail.com",
            "passwordSet": true
        },
        "note": "Check that inbox - and the spam folder. Nothing was saved."
    }
}
If something goes wrong
Codemessage you get back
503 Email is not set up on this server (fill in the smtp_* values in config/secrets.php)
502 The mail server refused the message - the reply includes the error and a hint
401 Please login first (missing or expired token)
POST Change password Login required
api/auth/change_password.php

For the settings screen, when the user is already logged in and knows their current password.

What you send
Headers
Authorization: Bearer <your token>
Content-Type: application/json
Fields in the JSON body
FieldTypeRequiredWhat it means
currentPassword string required The password they use right now
newPassword string required At least 6 characters
Example request body
{
    "currentPassword": "admin123",
    "newPassword": "admin456"
}
What you get back
HTTP 200  ·  Content-Type: application/json
{
    "success": true,
    "message": "Password updated successfully",
    "data": null
}
If something goes wrong
Codemessage you get back
401 Your current password is incorrect
422 New password must be at least 6 characters
POST Logout Login required
api/auth/logout.php

Deletes the current token from the database so it can never be used again. The app should also delete its saved copy. Send deviceToken as well and this phone stops receiving push notifications - otherwise the next person to use it keeps getting this family's messages.

What you send
Headers
Authorization: Bearer <your token>
Content-Type: application/json
Fields in the JSON body
FieldTypeRequiredWhat it means
deviceToken string optional The Firebase token of this phone, so push is switched off for it too
Example request body
{
    "deviceToken": "fMEP0vJ9S...long token from Firebase"
}
What you get back
HTTP 200  ·  Content-Type: application/json
{
    "success": true,
    "message": "Logged out successfully",
    "data": null
}
If something goes wrong
Codemessage you get back
401 Please login first (missing or expired token)
POST Delete account Login required
api/auth/delete_account.php

Removes the logged-in user for good, along with their expenses and receipt photos. The password is asked for again because this cannot be undone. If you are the LAST person in the household, the whole household goes with you. If you are the only admin and other members remain, you must nominate a successor with transferTo - they become the admin first.

What you send
Headers
Authorization: Bearer <your token>
Content-Type: application/json
Fields in the JSON body
FieldTypeRequiredWhat it means
password string required Your own password, typed again to confirm
transferTo int optional Id of the member who should become admin. Only needed when you are the last admin and other members remain
Example request body
{
    "password": "secret123",
    "transferTo": 2
}
What you get back
HTTP 200  ·  Content-Type: application/json
{
    "success": true,
    "message": "Your account has been deleted",
    "data": null
}
If something goes wrong
Codemessage you get back
401 That password is not correct
400 You are the only admin. Choose which member should take over before deleting your account.
422 Please fill all required fields

Dashboard and Profile

One call fills the whole home screen - no need to call members and expenses separately.

GET Dashboard Login required
api/dashboard.php

Everything the home screen needs in ONE request: the logged-in user, the family budget, how much was spent this month, every member with their own numbers, the 5 newest expenses, spending grouped by category for the chart, and the unread count for the bell icon.

What you send
Headers
Authorization: Bearer <your token>
Fields

Nothing to send - just the headers above.

What you get back
HTTP 200  ·  Content-Type: application/json
{
    "success": true,
    "message": "Dashboard loaded",
    "data": {
        "user": {
            "id": 1,
            "householdId": 1,
            "name": "Abdul Rehman",
            "email": "admin@smartspend.com",
            "role": "ADMIN",
            "assignedBudget": 50000
        },
        "household": {
            "id": 1,
            "name": "Khan Family",
            "monthlyBudget": 150000,
            "totalSpent": 63850,
            "remaining": 86150,
            "usedPercent": 42.60000000000000142108547152020037174224853515625,
            "expenseCount": 11,
            "monthStart": "2026-08-01",
            "monthLabel": "August 2026"
        },
        "mySpending": {
            "assignedBudget": 50000,
            "totalSpent": 27700,
            "remaining": 22300
        },
        "members": [
            {
                "id": 1,
                "name": "Abdul Rehman",
                "email": "admin@smartspend.com",
                "role": "ADMIN",
                "assignedBudget": 50000,
                "totalSpent": 27700,
                "remaining": 22300,
                "usedPercent": 55.39999999999999857891452847979962825775146484375,
                "lastActive": "Just now"
            },
            {
                "id": 2,
                "name": "Ayesha Khan",
                "email": "ayesha@smartspend.com",
                "role": "MEMBER",
                "assignedBudget": 30000,
                "totalSpent": 17500,
                "remaining": 12500,
                "usedPercent": 58.2999999999999971578290569595992565155029296875,
                "lastActive": "2 hours ago"
            }
        ],
        "recentExpenses": [
            {
                "id": 11,
                "title": "Bus Card Top-up",
                "amount": 1200,
                "category": "Transport",
                "date": "2026-08-12",
                "memberId": 4,
                "memberName": "Sara Khan"
            }
        ],
        "spendingByCategory": [
            {
                "category": "Groceries",
                "amount": 21900,
                "percent": 34.2999999999999971578290569595992565155029296875
            },
            {
                "category": "Education",
                "amount": 14750,
                "percent": 23.10000000000000142108547152020037174224853515625
            }
        ],
        "unreadCount": 2
    }
}
If something goes wrong
Codemessage you get back
401 Please login first (missing or expired token)
POST Edit my profile Login required
api/profile.php

Lets a user change their OWN name or email. Send only the field you want to change - anything you leave out keeps its old value.

What you send
Headers
Authorization: Bearer <your token>
Content-Type: application/json
Fields in the JSON body
FieldTypeRequiredWhat it means
name string optional New display name
email string optional New email. Must not belong to someone else
Example request body
{
    "name": "Abdul Rehman Khan"
}
What you get back
HTTP 200  ·  Content-Type: application/json
{
    "success": true,
    "message": "Profile updated successfully",
    "data": {
        "user": {
            "id": 1,
            "householdId": 1,
            "name": "Abdul Rehman Khan",
            "email": "admin@smartspend.com",
            "role": "ADMIN",
            "assignedBudget": 50000
        }
    }
}
If something goes wrong
Codemessage you get back
422 Please enter a valid email address
409 This email is already used by someone else

Expenses

By default every list shows only the CURRENT month. Add allMonths=1 to see everything.

GET List expenses Login required
api/expenses/list.php?category=Bills&page=1&limit=20

The main expense list. Every filter below is optional and they can be combined, for example ?memberId=3&category=Bills&page=2

What you send
Headers
Authorization: Bearer <your token>
Values added to the URL
FieldTypeRequiredWhat it means
memberId int optional Only this member's expenses
category string optional Groceries, Bills, Transport, Food, Health, Education, Shopping, Rent, Other
search string optional Search inside the expense title
from date optional Start date, format 2026-08-01
to date optional End date, format 2026-08-31
allMonths 0 or 1 optional Send 1 to ignore the current-month filter
page int optional Page number. Default 1
limit int optional Rows per page. Default 50, maximum 100
Example request
GET api/expenses/list.php?category=Bills&page=1&limit=20
What you get back
HTTP 200  ·  Content-Type: application/json
{
    "success": true,
    "message": "Expenses loaded",
    "data": {
        "expenses": [
            {
                "id": 12,
                "title": "Internet Bill",
                "amount": 4500,
                "category": "Bills",
                "date": "2026-08-18",
                "memberId": 1,
                "memberName": "Abdul Rehman"
            },
            {
                "id": 7,
                "title": "Mobile Load",
                "amount": 1500,
                "category": "Bills",
                "date": "2026-08-08",
                "memberId": 3,
                "memberName": "Hamza Khan"
            }
        ],
        "totalCount": 3,
        "totalSpent": 15200,
        "page": 1,
        "limit": 20,
        "totalPages": 1,
        "categories": [
            "Groceries",
            "Bills",
            "Transport",
            "Food",
            "Health",
            "Education",
            "Shopping",
            "Rent",
            "Other"
        ]
    }
}
If something goes wrong
Codemessage you get back
401 Please login first (missing or expired token)
GET Get one expense Login required
api/expenses/get.php?id=12

A single expense, in the same shape the lists use. The edit screen loads this before it fills in the form. canEdit tells you whether THIS user is allowed to change it (their own expense, or any expense if they are the admin), so the app can grey the controls out instead of letting somebody fill in a form the server will refuse.

What you send
Headers
Authorization: Bearer <your token>
Values added to the URL
FieldTypeRequiredWhat it means
id int required The expense id
Example request
GET api/expenses/get.php?id=12
What you get back
HTTP 200  ·  Content-Type: application/json
{
    "success": true,
    "message": "Expense loaded",
    "data": {
        "expense": {
            "id": 12,
            "title": "Internet Bill",
            "amount": 4500,
            "category": "Bills",
            "date": "2026-08-18",
            "memberId": 1,
            "memberName": "Abdul Rehman",
            "imageUrl": null
        },
        "canEdit": true
    }
}
If something goes wrong
Codemessage you get back
404 Expense not found
POST Add expense Login required
api/expenses/add.php

Saves a new expense. It also creates a notification for the family, and if the member goes past their assigned budget it creates a Budget Exceeded warning too. The response tells you overBudget so the app can show a message straight away.

What you send
Headers
Authorization: Bearer <your token>
Content-Type: application/json
Fields in the JSON body
FieldTypeRequiredWhat it means
title string required What the money was spent on
amount number required Must be greater than 0
category string optional One of the 9 categories. Anything else becomes "Other"
date date optional Format 2026-08-18. Defaults to today
image file optional Photo of the receipt. Send the request as multipart/form-data. JPG, PNG or WebP, up to 5 MB. The saved expense comes back with imageUrl filled in
(reply) note optional The reply carries outsideCurrentMonth. It is true when the date you sent is BEFORE the household month_start_date - the expense is saved, but every "this month" total filters it out, so the dashboard will not show it. monthStart comes back too so you can word the message. Show it, or the save looks like it did nothing.
memberId int optional Who the expense belongs to. Defaults to you. Only an ADMIN may put someone else's id here
Example request body
{
    "title": "Internet Bill",
    "amount": 4500,
    "category": "Bills",
    "date": "2026-08-18"
}
What you get back
HTTP 201  ·  Content-Type: application/json
{
    "success": true,
    "message": "Expense added successfully",
    "data": {
        "expense": {
            "id": 12,
            "title": "Internet Bill",
            "amount": 4500,
            "category": "Bills",
            "date": "2026-08-18",
            "memberId": 1,
            "memberName": "Abdul Rehman"
        },
        "memberSpent": 32200,
        "memberBudget": 50000,
        "overBudget": false
    }
}
If something goes wrong
Codemessage you get back
422 Please fill all required fields
422 Amount must be greater than 0
422 Date must look like 2026-08-19
403 You can only add expenses for yourself
404 That member is not part of your household
POST Edit expense Login required
api/expenses/update.php

Changes an existing expense. Send only the fields you want to change. A normal member can only edit their own expenses; an ADMIN can edit any expense in the household.

What you send
Headers
Authorization: Bearer <your token>
Content-Type: application/json
Fields in the JSON body
FieldTypeRequiredWhat it means
id int required Which expense to edit
title string optional New title
amount number optional New amount, greater than 0
category string optional New category
date date optional New date, format 2026-08-18
image file optional A new receipt photo (multipart/form-data). Replaces the old one, which is deleted from the server
removeImage 0 or 1 optional Send 1 to delete the receipt photo without adding a new one
Example request body
{
    "id": 12,
    "title": "Internet Bill (August)",
    "amount": 5000
}
What you get back
HTTP 200  ·  Content-Type: application/json
{
    "success": true,
    "message": "Expense updated successfully",
    "data": {
        "expense": {
            "id": 12,
            "title": "Internet Bill (August)",
            "amount": 5000,
            "category": "Bills",
            "date": "2026-08-18",
            "memberId": 1,
            "memberName": "Abdul Rehman"
        }
    }
}
If something goes wrong
Codemessage you get back
404 Expense not found
403 You can only edit your own expenses
422 Amount must be greater than 0
POST Delete expense Login required
api/expenses/delete.php

Removes one expense. A normal member can only delete their own; an ADMIN can delete any.

What you send
Headers
Authorization: Bearer <your token>
Content-Type: application/json
Fields in the JSON body
FieldTypeRequiredWhat it means
id int required Which expense to delete
Example request body
{
    "id": 12
}
What you get back
HTTP 200  ·  Content-Type: application/json
{
    "success": true,
    "message": "Expense deleted successfully",
    "data": null
}
If something goes wrong
Codemessage you get back
404 Expense not found
403 You can only delete your own expenses
GET Category list No login needed
api/expenses/categories.php

The 9 categories for the dropdown on the Add Expense screen. No login needed, so the app can load it early.

What you send
Headers
(no special headers needed)
Fields

Nothing to send - just the headers above.

What you get back
HTTP 200  ·  Content-Type: application/json
{
    "success": true,
    "message": "Categories loaded",
    "data": {
        "categories": [
            "Groceries",
            "Bills",
            "Transport",
            "Food",
            "Health",
            "Education",
            "Shopping",
            "Rent",
            "Other"
        ]
    }
}

Family Members

Anyone logged in can VIEW members. Only the ADMIN can add, edit or remove them.

GET List members Login required
api/members/list.php

Every member of the household with their assigned budget, how much they spent this month, what is left, the percentage used and when they were last active. The admin is always first.

What you send
Headers
Authorization: Bearer <your token>
Fields

Nothing to send - just the headers above.

What you get back
HTTP 200  ·  Content-Type: application/json
{
    "success": true,
    "message": "Members loaded",
    "data": {
        "members": [
            {
                "id": 1,
                "name": "Abdul Rehman",
                "email": "admin@smartspend.com",
                "role": "ADMIN",
                "assignedBudget": 50000,
                "totalSpent": 27700,
                "remaining": 22300,
                "usedPercent": 55.39999999999999857891452847979962825775146484375,
                "lastActive": "Just now"
            },
            {
                "id": 3,
                "name": "Hamza Khan",
                "email": "hamza@smartspend.com",
                "role": "MEMBER",
                "assignedBudget": 25000,
                "totalSpent": 12800,
                "remaining": 12200,
                "usedPercent": 51.2000000000000028421709430404007434844970703125,
                "lastActive": "1 day ago"
            }
        ],
        "totalMembers": 4
    }
}
If something goes wrong
Codemessage you get back
401 Please login first (missing or expired token)
GET Member detail Login required
api/members/get.php?id=3

One member plus all of their expenses this month and their spending grouped by category. Use this for the Member Detail screen.

What you send
Headers
Authorization: Bearer <your token>
Values added to the URL
FieldTypeRequiredWhat it means
id int required Which member to load
Example request
GET api/members/get.php?id=3
What you get back
HTTP 200  ·  Content-Type: application/json
{
    "success": true,
    "message": "Member loaded",
    "data": {
        "member": {
            "id": 3,
            "name": "Hamza Khan",
            "email": "hamza@smartspend.com",
            "role": "MEMBER",
            "assignedBudget": 25000,
            "totalSpent": 12800,
            "remaining": 12200,
            "usedPercent": 51.2000000000000028421709430404007434844970703125,
            "lastActive": "1 day ago"
        },
        "expenses": [
            {
                "id": 8,
                "title": "Cricket Kit",
                "amount": 4800,
                "category": "Shopping",
                "date": "2026-08-11",
                "memberId": 3,
                "memberName": "Hamza Khan"
            }
        ],
        "expenseCount": 3,
        "spendingByCategory": [
            {
                "category": "Transport",
                "amount": 6500
            },
            {
                "category": "Shopping",
                "amount": 4800
            },
            {
                "category": "Bills",
                "amount": 1500
            }
        ]
    }
}
If something goes wrong
Codemessage you get back
422 Please fill all required fields
404 Member not found
GET Member expenses only Login required
api/members/expenses.php?id=3

A lighter version of Member detail - just the expense list and the total. Useful for a "see all" screen.

What you send
Headers
Authorization: Bearer <your token>
Values added to the URL
FieldTypeRequiredWhat it means
id int required Which member
allMonths 0 or 1 optional Send 1 to include older months as well
Example request
GET api/members/expenses.php?id=3
What you get back
HTTP 200  ·  Content-Type: application/json
{
    "success": true,
    "message": "Member expenses loaded",
    "data": {
        "expenses": [
            {
                "id": 6,
                "title": "Petrol",
                "amount": 6500,
                "category": "Transport",
                "date": "2026-08-04",
                "memberId": 3,
                "memberName": "Hamza Khan"
            }
        ],
        "totalSpent": 12800,
        "count": 3
    }
}
If something goes wrong
Codemessage you get back
404 Member not found
POST Add member Admin only
api/members/add.php

The admin creates a login for a family member. The member can then login with the email and password given here. New members are always MEMBER: A household has exactly one admin - whoever created it at signup - so role is not accepted here. The only handover is auth/delete_account.php, where an admin deleting their own account has to name a successor first.

What you send
Headers
Authorization: Bearer <your token>
Content-Type: application/json
Fields in the JSON body
FieldTypeRequiredWhat it means
name string required Full name
email string required Must be valid and not already registered
password string required At least 6 characters. Give it to the member
assignedBudget number optional Their spending limit. Default 0
Example request body
{
    "name": "Bilal Khan",
    "email": "bilal@smartspend.com",
    "password": "bilal123",
    "assignedBudget": 15000
}
What you get back
HTTP 201  ·  Content-Type: application/json
{
    "success": true,
    "message": "Member added successfully",
    "data": {
        "member": {
            "id": 5,
            "name": "Bilal Khan",
            "email": "bilal@smartspend.com",
            "role": "MEMBER",
            "assignedBudget": 15000,
            "totalSpent": 0,
            "remaining": 15000,
            "usedPercent": 0,
            "lastActive": "Never"
        }
    }
}
If something goes wrong
Codemessage you get back
403 Only the household admin can do this
422 Please fill all required fields
422 Password must be at least 6 characters
409 This email is already registered
POST Edit member Admin only
api/members/update.php

Changes a member's name, email or budget. Send only what you want to change. A household has exactly one admin - whoever created it at signup - so role is not accepted here. The only handover is auth/delete_account.php, where an admin deleting their own account has to name a successor first. Sending the role the member already has is fine, so posting a whole form back still works; only an actual change is refused. Lowering a budget below what has already been spent is ALLOWED - spent money cannot be un-spent, and the old figure may simply have been typed wrong - but the response says so: overBudget is true and shortfall is how far past the new limit the spending already is, so the screen can confirm before saving. The member gets a BUDGET_WARNING notification instead of the usual INFO one.

What you send
Headers
Authorization: Bearer <your token>
Content-Type: application/json
Fields in the JSON body
FieldTypeRequiredWhat it means
id int required Which member to edit
name string optional New name
email string optional New email
assignedBudget number optional New budget
Example request body
{
    "id": 2,
    "name": "Ayesha K.",
    "assignedBudget": 32000
}
What you get back
HTTP 200  ·  Content-Type: application/json
{
    "success": true,
    "message": "Member updated successfully",
    "data": {
        "member": {
            "id": 2,
            "name": "Ayesha K.",
            "email": "ayesha@smartspend.com",
            "role": "MEMBER",
            "assignedBudget": 32000,
            "totalSpent": 17500,
            "remaining": 14500,
            "usedPercent": 54.7000000000000028421709430404007434844970703125,
            "lastActive": "2 hours ago"
        },
        "spent": 17500,
        "overBudget": false,
        "shortfall": 0
    }
}
If something goes wrong
Codemessage you get back
403 Only the household admin can do this
404 Member not found
409 This email is already used by someone else
422 The household admin cannot be changed
POST Set member budget Admin only
api/members/update_budget.php

The quick version of Edit member for the "Assign Budget" screen - it only changes the money. The response comes back with the member's fresh spending numbers so the screen can refresh immediately. Lowering a budget below what has already been spent is ALLOWED - spent money cannot be un-spent, and the old figure may simply have been typed wrong - but the response says so: overBudget is true and shortfall is how far past the new limit the spending already is, so the screen can confirm before saving. The member gets a BUDGET_WARNING notification instead of the usual INFO one.

What you send
Headers
Authorization: Bearer <your token>
Content-Type: application/json
Fields in the JSON body
FieldTypeRequiredWhat it means
id int required Which member
assignedBudget number required The new limit. Cannot be negative
Example request body
{
    "id": 3,
    "assignedBudget": 26000
}
What you get back
HTTP 200  ·  Content-Type: application/json
{
    "success": true,
    "message": "Budget updated successfully",
    "data": {
        "member": {
            "id": 3,
            "name": "Hamza Khan",
            "email": "hamza@smartspend.com",
            "role": "MEMBER",
            "assignedBudget": 26000,
            "totalSpent": 12800,
            "remaining": 13200,
            "usedPercent": 49.2000000000000028421709430404007434844970703125,
            "lastActive": "1 day ago"
        },
        "spent": 12800,
        "overBudget": false,
        "shortfall": 0
    }
}
If something goes wrong
Codemessage you get back
403 Only the household admin can do this
422 Budget cannot be negative
404 Member not found
POST Remove member Admin only
api/members/delete.php

Deletes the member AND all of their expenses and tokens. The admin cannot delete themselves.

What you send
Headers
Authorization: Bearer <your token>
Content-Type: application/json
Fields in the JSON body
FieldTypeRequiredWhat it means
id int required Which member to remove
Example request body
{
    "id": 5
}
What you get back
HTTP 200  ·  Content-Type: application/json
{
    "success": true,
    "message": "Member removed successfully",
    "data": null
}
If something goes wrong
Codemessage you get back
403 Only the household admin can do this
400 You cannot remove yourself
404 Member not found
POST Reset a member password Admin only
api/members/reset_password.php

The admin hands out the passwords when they add members, so sooner or later somebody forgets theirs. This sets a new one without going through the email code. The member is logged out of every device and told what happened, so it can never be done quietly behind their back. Use change_password.php for your own password.

What you send
Headers
Authorization: Bearer <your token>
Content-Type: application/json
Fields in the JSON body
FieldTypeRequiredWhat it means
id int required Which member
newPassword string required At least 6 characters
Example request body
{
    "id": 3,
    "newPassword": "freshstart123"
}
What you get back
HTTP 200  ·  Content-Type: application/json
{
    "success": true,
    "message": "Password reset. Give the new password to Hamza Khan.",
    "data": {
        "member": {
            "id": 3,
            "name": "Hamza Khan",
            "email": "hamza@smartspend.com"
        }
    }
}
If something goes wrong
Codemessage you get back
422 Password must be at least 6 characters
403 Only the household admin can do this
404 Member not found
400 Use change_password.php to change your own password

Household

The household is the family itself - its name, its monthly budget and which month is running.

GET Get household Login required
api/household/get.php

The family name, monthly budget, how much is spent and left this month, and how many members there are.

What you send
Headers
Authorization: Bearer <your token>
Fields

Nothing to send - just the headers above.

What you get back
HTTP 200  ·  Content-Type: application/json
{
    "success": true,
    "message": "Household loaded",
    "data": {
        "household": {
            "id": 1,
            "name": "Khan Family",
            "monthlyBudget": 150000,
            "totalSpent": 63850,
            "remaining": 86150,
            "usedPercent": 42.60000000000000142108547152020037174224853515625,
            "monthStart": "2026-08-01",
            "monthLabel": "August 2026",
            "memberCount": 4
        }
    }
}
If something goes wrong
Codemessage you get back
401 Please login first (missing or expired token)
POST Edit household Admin only
api/household/update.php

Changes the family name or the total monthly budget. Send only what you want to change. Lowering a budget below what has already been spent is ALLOWED - spent money cannot be un-spent, and the old figure may simply have been typed wrong - but the response says so: overBudget is true and shortfall is how far past the new limit the spending already is, so the screen can confirm before saving. The family gets a BUDGET_WARNING notification instead of the usual INFO one.

What you send
Headers
Authorization: Bearer <your token>
Content-Type: application/json
Fields in the JSON body
FieldTypeRequiredWhat it means
name string optional New family name
monthlyBudget number optional New total budget. Cannot be negative
Example request body
{
    "name": "Khan Family",
    "monthlyBudget": 160000
}
What you get back
HTTP 200  ·  Content-Type: application/json
{
    "success": true,
    "message": "Household updated successfully",
    "data": {
        "household": {
            "id": 1,
            "name": "Khan Family",
            "monthlyBudget": 160000
        },
        "spent": 92400,
        "overBudget": false,
        "shortfall": 0
    }
}
If something goes wrong
Codemessage you get back
403 Only the household admin can do this
422 Budget cannot be negative
POST Start new month Admin only
api/household/new_month.php

The Start New Month button. It does NOT delete anything - old expenses stay in the database so the report screen still works. It moves month_start_date to the FIRST of the current month, and every "this month" figure counts from there. It used to move it to TODAY, which meant pressing the button on the 27th made all 26 earlier days vanish from the dashboard and from every member total - nothing was deleted, but everything read zero and there was no undo. Pressing it twice in the same month is now a harmless no-op: monthChanged comes back false and only the budget is saved.

What you send
Headers
Authorization: Bearer <your token>
Content-Type: application/json
Fields in the JSON body
FieldTypeRequiredWhat it means
monthlyBudget number optional Set a different budget for the new month. Leave it out to keep the same one
startDate date optional Only if your household does not budget from the 1st - e.g. payday on the 25th. Format 2026-08-25. Cannot be in the future. Leave it out and the 1st of this month is used
Example request body
{
    "monthlyBudget": 160000
}
What you get back
HTTP 200  ·  Content-Type: application/json
{
    "success": true,
    "message": "New month started successfully",
    "data": {
        "closedMonth": {
            "from": "2026-08-01",
            "to": "2026-09-01",
            "totalSpent": 63850,
            "expenseCount": 11
        },
        "newMonthStart": "2026-09-01",
        "monthlyBudget": 160000,
        "monthChanged": true
    }
}
If something goes wrong
Codemessage you get back
403 Only the household admin can do this
422 Date must look like 2026-08-01
422 A month cannot start in the future - nothing would be counted until then.
GET Reports Login required
api/household/report.php?months=6

Totals for the reports and charts screen: month by month, then per member, then per category. Unlike the other endpoints this one looks at ALL months, not just the current one.

What you send
Headers
Authorization: Bearer <your token>
Values added to the URL
FieldTypeRequiredWhat it means
months int optional How many months of history. Default 6, maximum 24
Example request
GET api/household/report.php?months=6
What you get back
HTTP 200  ·  Content-Type: application/json
{
    "success": true,
    "message": "Report loaded",
    "data": {
        "monthly": [
            {
                "month": "2026-08",
                "label": "August 2026",
                "totalSpent": 63850,
                "expenseCount": 11
            },
            {
                "month": "2026-07",
                "label": "July 2026",
                "totalSpent": 58200,
                "expenseCount": 9
            }
        ],
        "byMember": [
            {
                "memberId": 1,
                "name": "Abdul Rehman",
                "role": "ADMIN",
                "totalSpent": 32700,
                "expenseCount": 3
            },
            {
                "memberId": 2,
                "name": "Ayesha Khan",
                "role": "MEMBER",
                "totalSpent": 17500,
                "expenseCount": 3
            }
        ],
        "byCategory": [
            {
                "category": "Groceries",
                "totalSpent": 21900,
                "expenseCount": 2
            },
            {
                "category": "Bills",
                "totalSpent": 15700,
                "expenseCount": 3
            }
        ]
    }
}
If something goes wrong
Codemessage you get back
401 Please login first (missing or expired token)

Notifications

The API creates these by itself when an expense is added, a member joins, a budget is passed or a new month starts.

GET List notifications Login required
api/notifications/list.php?limit=20

Messages for this user plus the ones sent to the whole family. Newest first. type is INFO, EXPENSE_ADDED or BUDGET_WARNING so the app can show a different icon for each.

What you send
Headers
Authorization: Bearer <your token>
Values added to the URL
FieldTypeRequiredWhat it means
limit int optional How many to return. Default 50, maximum 100
Example request
GET api/notifications/list.php?limit=20
What you get back
HTTP 200  ·  Content-Type: application/json
{
    "success": true,
    "message": "Notifications loaded",
    "data": {
        "notifications": [
            {
                "id": 4,
                "title": "New Expense Added",
                "message": "Abdul Rehman added \"Internet Bill\" for Rs 4,500.00",
                "type": "EXPENSE_ADDED",
                "isRead": false,
                "createdAt": "2026-08-19 01:52:14",
                "timeAgo": "Just now"
            },
            {
                "id": 2,
                "title": "Budget Warning",
                "message": "Hamza Khan has used more than 50% of the assigned budget.",
                "type": "BUDGET_WARNING",
                "isRead": true,
                "createdAt": "2026-08-19 01:29:03",
                "timeAgo": "23 mins ago"
            }
        ],
        "unreadCount": 1
    }
}
If something goes wrong
Codemessage you get back
401 Please login first (missing or expired token)
POST Mark as read Login required
api/notifications/mark_read.php

Send id to mark ONE notification as read, or send all=1 to clear the whole list. Use all=1 behind a "Mark all as read" button.

What you send
Headers
Authorization: Bearer <your token>
Content-Type: application/json
Fields in the JSON body
FieldTypeRequiredWhat it means
id int optional Which notification. Required unless you send all
all 0 or 1 optional Send 1 to mark every notification as read
Example request body
{
    "all": 1
}
What you get back
HTTP 200  ·  Content-Type: application/json
{
    "success": true,
    "message": "All notifications marked as read",
    "data": null
}
If something goes wrong
Codemessage you get back
422 Please fill all required fields
404 Notification not found
POST Send your own notification Admin only
api/notifications/send.php

The admin writes a message themselves, e.g. "Please add your bills before Friday". It appears on the Notifications screen AND is pushed to the phones. Leave memberId out to send it to the whole family.

What you send
Headers
Authorization: Bearer <your token>
Content-Type: application/json
Fields in the JSON body
FieldTypeRequiredWhat it means
title string required Short heading, up to 150 characters
message string required The message itself
memberId int optional Send to one member only. Leave it out for everybody
type string optional INFO, BUDGET_WARNING or EXPENSE_ADDED. Default INFO
Example request body
{
    "title": "Add your bills",
    "message": "Please add this month&#039;s bills before Friday."
}
What you get back
HTTP 201  ·  Content-Type: application/json
{
    "success": true,
    "message": "Notification sent",
    "data": {
        "notificationId": 12,
        "sentTo": "Everyone in the household",
        "devicesNotified": 3,
        "pushEnabled": true
    }
}
If something goes wrong
Codemessage you get back
422 Please fill all required fields
403 Only the household admin can do this
404 That member is not part of your household

Push Notifications

Firebase Cloud Messaging. The app registers its device token once after login, and from then on every notification the API creates is also pushed to the phone - even when the app is closed. Nothing else changes: the same messages still appear on the Notifications screen.

POST Register this phone Login required
api/devices/register.php

Call this right after login with the token Firebase gave the app (FirebaseMessaging.getInstance().token), and again whenever onNewToken() fires. Sending the same token twice is safe - the row is updated, never duplicated. Careful: the field is called deviceToken, because token already means the login token everywhere else.

What you send
Headers
Authorization: Bearer <your token>
Content-Type: application/json
Fields in the JSON body
FieldTypeRequiredWhat it means
deviceToken string required The FCM registration token from the app (20-255 characters)
platform string optional android, ios or web. Default android
deviceName string optional Anything you like, e.g. "Samsung A15". Only for your own records
Example request body
{
    "deviceToken": "fMEP0vJ9S...long token from Firebase",
    "platform": "android",
    "deviceName": "Samsung A15"
}
What you get back
HTTP 200  ·  Content-Type: application/json
{
    "success": true,
    "message": "Device registered for push notifications",
    "data": {
        "registered": true,
        "platform": "android",
        "deviceCount": 1
    }
}
If something goes wrong
Codemessage you get back
422 That does not look like a Firebase device token
401 Please login first (missing or expired token)
POST Stop pushes to this phone Login required
api/devices/unregister.php

Call this when the user logs out, otherwise the next person to use the phone keeps receiving this family's notifications. (api/auth/logout.php does the same thing if you send it deviceToken.) Send all=1 to sign every device of this user out of push.

What you send
Headers
Authorization: Bearer <your token>
Content-Type: application/json
Fields in the JSON body
FieldTypeRequiredWhat it means
deviceToken string optional The device to remove. Required unless you send all
all 0 or 1 optional Send 1 to remove every device of this user
Example request body
{
    "deviceToken": "fMEP0vJ9S...long token from Firebase"
}
What you get back
HTTP 200  ·  Content-Type: application/json
{
    "success": true,
    "message": "Device removed from push notifications",
    "data": {
        "removed": 1
    }
}
If something goes wrong
Codemessage you get back
422 Please fill all required fields
401 Please login first (missing or expired token)
POST Send a test push Login required
api/devices/test.php

Sends a real push to your own phones immediately and tells you exactly what Firebase answered. Use this first when push "is not working" - the errors list gives the real reason. Nothing is saved to the notifications table.

What you send
Headers
Authorization: Bearer <your token>
Content-Type: application/json
Fields in the JSON body
FieldTypeRequiredWhat it means
title string optional Heading. Default "SmartSpend test"
message string optional Body text. Default explains that push works
Example request body
{
    "title": "SmartSpend test",
    "message": "Testing 1 2 3"
}
What you get back
HTTP 200  ·  Content-Type: application/json
{
    "success": true,
    "message": "Test push sent to 1 device(s)",
    "data": {
        "devicesTried": 1,
        "sent": 1,
        "failed": 0,
        "removed": 0,
        "errors": []
    }
}
If something goes wrong
Codemessage you get back
404 No device registered for this account yet
502 Firebase did not accept the message (the reason is in errors)
503 Push is not set up on this server (no key in config/)
POST Send a test push to a member Admin only
api/devices/test_to_user.php

The admin picks a member and pushes to THAT person's phones immediately. Same live report as the endpoint above, but aimed at somebody else - use it to prove push works for a member, not just for yourself. Nothing is saved to the notifications table. There is a click-it version of this at admin/push_test.php.

What you send
Headers
Authorization: Bearer <your token>
Content-Type: application/json
Fields in the JSON body
FieldTypeRequiredWhat it means
userId int required Which member to push to. Must be in your household
title string optional Heading. Default "SmartSpend test"
message string optional Body text. Default explains that push works
Example request body
{
    "userId": 3,
    "title": "SmartSpend test",
    "message": "Testing 1 2 3"
}
What you get back
HTTP 200  ·  Content-Type: application/json
{
    "success": true,
    "message": "Test push sent to Hamza Khan (1 device(s))",
    "data": {
        "memberId": 3,
        "member": "Hamza Khan",
        "devicesTried": 1,
        "sent": 1,
        "failed": 0,
        "removed": 0,
        "errors": [],
        "serverProject": "smartspend-e8aef"
    }
}
If something goes wrong
Codemessage you get back
403 Only the household admin can do this
404 That member is not part of your household
404 That member has no registered device yet
422 Please fill all required fields
502 Firebase did not accept the message (the reason is in errors)
502 Push is set up against the wrong Firebase project (errors.fix says what to do)
503 Push is not set up on this server (no key in config/)

Debug mode is OFF. Because this is the live server, database errors are hidden and Forgot password no longer returns the code in the response.