Development: http://localhost:3000
Production: Configured via PUBLIC_API_URL
All endpoints except POST /auth/firebase require authentication via Authorization: Bearer <firebase-id-token> header.
Success:
{
"success": true,
"data": { ... }
}Error:
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable message"
}
}Authenticate with Firebase ID Token.
Auth: Authorization: Bearer <firebase-id-token> header (from Firebase getIdToken())
Request: Empty body (token is in Authorization header).
Response:
{
"success": true,
"data": {
"id": "uuid",
"firebase_uid": "abc123...",
"email": "user@example.com",
"name": "User Name",
"avatar": "https://...",
"upi_id": "user@upi",
"is_profile_complete": true,
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z"
}
}Errors: 401 if token is invalid or expired.
Get current user profile.
Auth: Required
Response:
{
"success": true,
"data": {
"id": "uuid",
"google_id": "123456789",
"email": "user@example.com",
"name": "User Name",
"avatar": "https://...",
"upi_id": "user@upi",
"is_profile_complete": true,
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z"
}
}Errors: 401 if not authenticated.
Update current user profile.
Auth: Required
Request:
{
"upi_id": "user@upi"
}Validation:
upi_id: Required, valid UPI ID format (e.g.,user@provider)
Response: Updated user object.
Errors: 400 if invalid UPI format.
Create a new group.
Auth: Required
Request:
{
"name": "Trip to Goa"
}Validation:
name: Required, non-empty string
Response:
{
"success": true,
"data": {
"id": "uuid",
"name": "Trip to Goa",
"owner_id": "uuid",
"invite_code": "A82DKP",
"created_at": "2024-01-01T00:00:00Z"
}
}List all groups for the authenticated user.
Auth: Required
Response:
{
"success": true,
"data": [
{
"id": "uuid",
"name": "Trip to Goa",
"owner_id": "uuid",
"invite_code": "A82DKP",
"member_count": 4,
"created_at": "2024-01-01T00:00:00Z"
}
]
}Get group details including members, expenses, balances, and settlements.
Auth: Required (must be a member)
Response:
{
"success": true,
"data": {
"group": { ... },
"members": [
{
"user_id": "uuid",
"name": "User Name",
"email": "user@example.com",
"avatar": "https://...",
"joined_at": "2024-01-01T00:00:00Z"
}
],
"expenses": [
{
"id": "uuid",
"description": "Dinner",
"amount": 1200,
"paid_by": "uuid",
"paid_by_name": "User Name",
"created_by": "uuid",
"split_type": "equal",
"participants": [
{
"user_id": "uuid",
"share_amount": 300
}
],
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z"
}
],
"balances": [
{
"user_id": "uuid",
"name": "User Name",
"net_balance": 250,
"paid": 600,
"share": 300,
"received": 0,
"paid_out": 50
}
],
"settlements": [
{
"id": "uuid",
"payer_id": "uuid",
"receiver_id": "uuid",
"amount": 250,
"expense_id": "uuid",
"created_at": "2024-01-01T00:00:00Z"
}
],
"obligations": [
{
"id": "uuid_participantId",
"expenseId": "uuid",
"expenseTitle": "Dinner",
"payerId": "uuid",
"payerName": "Payer Name",
"debtorId": "uuid",
"debtorName": "Debtor Name",
"amount": 250,
"createdAt": "2024-01-01T00:00:00Z"
}
],
"simplified_debts": [
{
"from": {
"user_id": "uuid",
"name": "User Name"
},
"to": {
"user_id": "uuid",
"name": "Other User"
},
"amount": 250
}
]
}
}Errors: 403 if not a member, 404 if group not found.
Delete a group. Only the owner can delete.
Auth: Required (must be owner)
Errors: 403 if not owner, 404 if not found.
Join a group using an invite code.
Auth: Required
Request:
{
"invite_code": "A82DKP"
}Validation:
invite_code: Required, non-empty string
Errors: 404 if invalid code, 409 if already a member or group full.
Leave a group.
Auth: Required
Errors: 409 if balance is not ₹0, 403 if owner (must delete instead).
Create an expense in a group.
The payer is always the authenticated user. The backend ignores any paid_by value sent by the client.
Auth: Required (must be a member)
Request (Equal Split):
{
"description": "Dinner",
"amount": 1200,
"split_type": "equal",
"participants": ["uuid1", "uuid2", "uuid3", "uuid4"]
}Request (Custom Split):
{
"description": "Dinner",
"amount": 1200,
"split_type": "custom",
"participants": [
{ "user_id": "uuid1", "share_amount": 100 },
{ "user_id": "uuid2", "share_amount": 500 },
{ "user_id": "uuid3", "share_amount": 300 },
{ "user_id": "uuid4", "share_amount": 300 }
]
}Validation:
description: Required, non-empty stringamount: Required, number > 0split_type: Required, "equal" or "custom"participants: Required array- Equal: array of user IDs, at least 1
- Custom: array of
{ user_id, share_amount }, sum must equal expense amount
- All participants must be group members
paid_byis NOT accepted — the payer is always the authenticated user
Edit an expense. Only the creator can edit. The payer is immutable and cannot be changed via this endpoint.
Auth: Required (must be creator)
Request: Same shape as create expense (without paid_by).
Note: The paid_by field is never updated — the original payer is preserved.
Errors: 403 if not creator, 404 if not found.
Delete an expense. Only the creator can delete.
Auth: Required (must be creator)
Errors: 403 if not creator, 404 if not found.
Record a settlement between two members.
Auth: Required (must be a member)
Request:
{
"payer_id": "uuid",
"receiver_id": "uuid",
"amount": 250
}Validation:
payer_id: Required, must be a group memberreceiver_id: Required, must be a group memberamount: Required, number > 0- Payer and receiver must be different
Errors: 400 if validation fails, 403 if not a member.