Development: http://localhost:3001/api/v1
Production: http://104.248.254.226/api/v1
Swagger UI: http://104.248.254.226/api/docs
All protected endpoints require a JWT token in the Authorization header:
Authorization: Bearer <access_token>
Register a new user.
Request:
{
"email": "user@example.com",
"username": "john_doe",
"password": "SecurePass123!",
"fullName": "John Doe"
}Response:
{
"success": true,
"data": {
"user": {
"id": "uuid",
"email": "user@example.com",
"username": "john_doe"
},
"tokens": {
"accessToken": "jwt_token",
"refreshToken": "refresh_token"
}
}
}Login with email and password.
Request:
{
"email": "user@example.com",
"password": "SecurePass123!"
}Response (2FA enabled):
{
"success": true,
"data": {
"requires2FA": true
}
}Response (2FA disabled or code provided):
{
"success": true,
"data": {
"user": { "id": "uuid", "email": "user@example.com" },
"tokens": {
"accessToken": "jwt_token",
"refreshToken": "refresh_token"
}
}
}Logout current user.
Refresh access token.
Request:
{
"refreshToken": "refresh_token"
}Generate a 2FA secret and QR code. Returns { secret, qrCode }.
Enable 2FA by verifying a TOTP code.
Request:
{
"code": "123456"
}Disable 2FA (requires valid TOTP code).
Request:
{
"code": "123456"
}Get current user profile.
Update current user profile.
Soft-delete account (30-day grace period before permanent deletion).
Cancel a pending account deletion during the 30-day grace period.
Export all user data (profile, expenses, incomes, perimeters). GDPR data portability.
Import user data (expenses and incomes).
Request:
{
"expenses": [{ "amount": 45.50, "description": "Groceries", "date": "2026-02-17" }],
"incomes": [{ "amount": 5000.00, "description": "Salary", "date": "2026-02-01" }]
}Record user consent for data processing.
Get all expenses for current user.
Query Parameters:
page: number (default: 1)limit: number (default: 20)categoryId: string (filter by category)startDate: ISO dateendDate: ISO date
Response:
{
"success": true,
"data": {
"items": [
{
"id": "uuid",
"amount": 45.50,
"currency": "USD",
"description": "Groceries",
"categoryId": "uuid",
"date": "2026-02-17",
"receiptUrl": "/uploads/receipts/userId/filename.jpg",
"createdAt": "2026-02-17T10:30:00Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 150
}
}
}Create new expense.
Headers (optional):
X-Idempotency-Key: string - prevents duplicate creationX-Client-Timestamp: number - epoch ms for offline LWW sync
Request:
{
"amount": 45.50,
"currency": "USD",
"description": "Groceries",
"categoryId": "uuid",
"date": "2026-02-17",
"paymentMethod": "credit_card",
"tags": ["food", "essentials"]
}Get expense by ID.
Update expense. If X-Client-Timestamp header is provided and the server record is newer, returns { data: existingRecord, conflict: true }.
Delete expense.
Batch create expenses (for offline sync).
Upload a receipt image for an expense.
Content-Type: multipart/form-data
Field name: receipt
Accepted types: image/jpeg, image/png, application/pdf
Max size: 5 MB
The uploaded file is virus-scanned (ClamAV) and optimized (sharp) before storage.
Download the receipt file for an expense.
Get all income records. Same pagination and filtering as expenses.
Create income record.
Headers (optional):
X-Idempotency-Key: stringX-Client-Timestamp: number
Request:
{
"amount": 5000.00,
"currency": "USD",
"description": "Monthly salary",
"source": "Company Inc",
"date": "2026-02-01",
"isRecurring": true,
"recurrenceRule": "monthly"
}Update income record. Supports LWW conflict detection.
Delete income record.
Get all perimeters for current user (owned + shared).
Create new perimeter.
Request:
{
"name": "Food & Dining",
"description": "All food-related expenses",
"icon": "utensils",
"color": "#FF6B6B",
"budget": 500.00,
"budgetPeriod": "monthly"
}Get perimeter by ID (respects permission matrix).
Update perimeter (requires manager+ role).
Soft delete perimeter (owner only).
Share perimeter with another user.
Request:
{
"userId": "uuid",
"role": "contributor"
}Roles: viewer, contributor, manager.
Revoke a user's access to a shared perimeter.
List all users a perimeter is shared with.
Get budget utilization for a perimeter.
Get all friends.
Get pending friend requests.
Send friend request.
Request:
{
"addresseeId": "uuid"
}Accept friend request.
Reject friend request.
Remove friend.
CQRS pattern: read-optimized queries via AnalyticsReadService, write operations via AnalyticsWriteService.
Get dashboard analytics.
Query Parameters:
startDate: ISO dateendDate: ISO date
Response:
{
"success": true,
"data": {
"totalExpenses": 1250.50,
"totalIncome": 5000.00,
"balance": 3749.50,
"expensesByCategory": [
{
"categoryId": "uuid",
"categoryName": "Food",
"amount": 450.00,
"percentage": 36
}
],
"recentTransactions": []
}
}Get expenses grouped by category.
Get expense trend over time.
Get cash flow (income vs expenses) over time.
Get all notifications for current user.
Mark notification as read.
Delete notification.
Get notification preferences.
Update notification preferences.
Request:
{
"budgetAlerts": true,
"recurringReminders": false,
"friendRequests": true,
"perimeterShares": true,
"preferredChannels": ["in-app"],
"quietHoursStart": "22:00",
"quietHoursEnd": "08:00"
}Get audit logs (admin only). Paginated, filterable by userId, method, entity.
All endpoints return consistent error responses. See ERROR_CODES.md for the full reference.
{
"success": false,
"code": "BAD_REQUEST",
"message": "Validation failed",
"details": ["amount must be a positive number"],
"path": "/api/v1/expenses",
"timestamp": "2026-02-20T12:00:00.000Z"
}| Code | Meaning |
|---|---|
| 200 | Success |
| 201 | Resource created |
| 400 | Invalid input |
| 401 | Missing or invalid token |
| 403 | Insufficient permissions |
| 404 | Resource not found |
| 409 | Conflict (duplicate resource or idempotency) |
| 422 | Validation failed |
| 429 | Rate limit exceeded |
| 500 | Server error |
Connect to: ws://localhost:3001 (dev) or ws://104.248.254.226 (prod, via /socket.io/ path)
Authentication: pass JWT token in the auth object.
expense:created- new expense createdexpense:updated- expense updatedexpense:deleted- expense deletedfriend:request- friend request receivednotification:new- new notification
import { io } from 'socket.io-client';
const socket = io('ws://localhost:3001', {
auth: { token: accessToken }
});
socket.on('expense:created', (data) => {
console.log('New expense:', data);
});