Complete REST API reference for Subly.
- API Overview
- Authentication
- Authentication Endpoints
- Subscription Endpoints
- Calendar Endpoints
- Invitation Endpoints
- Admin Endpoints
- Push Notification Endpoints
- Error Handling
- Rate Limiting
Base URL: http://localhost:5071/api (development)
Content-Type: application/json
Authentication: JWT Bearer token (except public endpoints)
Currently: v1 (implicit, no version in URL)
Success response:
{
"message": "Success message",
"data": { ... }
}Error response:
{
"message": "Error message",
"errors": [
{
"field": "email",
"message": "Please enter a valid email address"
}
]
}Most endpoints require authentication using JWT tokens.
Authorization: Bearer YOUR_JWT_TOKEN
Login or register to receive a JWT token:
POST /api/auth/login
POST /api/auth/registerToken is valid for 30 days.
curl -X GET http://localhost:5071/api/subscriptions \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."Create a new user account with invitation code.
Endpoint: POST /api/auth/register
Authentication: None (public)
Rate Limit: 3 requests per hour per IP
Request Body:
{
"username": "john_doe",
"email": "john@example.com",
"password": "SecurePass123",
"invitationCode": "ABC123"
}Validation:
username: min 3 charactersemail: valid email formatpassword: min 12 characters, must contain uppercase, lowercase, and numberinvitationCode: valid and unused invitation code
Response: 201 Created
{
"_id": "65f1234567890abcdef12345",
"username": "john_doe",
"email": "john@example.com",
"emailVerified": false,
"emailNotifications": true,
"pushNotificationsEnabled": false,
"paymentReminderDays": 3,
"language": "en",
"role": "user",
"currency": "EUR",
"theme": "dark",
"monthlyRevenue": 0,
"annualRevenue": 0,
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}Errors:
400: Invalid input data404: Invalid invitation code500: Server error
Authenticate user and receive JWT token.
Endpoint: POST /api/auth/login
Authentication: None (public)
Rate Limit: 5 requests per 15 minutes per IP
Request Body:
{
"username": "john_doe",
"password": "SecurePass123"
}Response: 200 OK
{
"_id": "65f1234567890abcdef12345",
"username": "john_doe",
"email": "john@example.com",
"emailVerified": true,
"emailNotifications": true,
"pushNotificationsEnabled": false,
"paymentReminderDays": 3,
"language": "en",
"role": "user",
"currency": "EUR",
"theme": "dark",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}Errors:
401: Invalid credentials403: Account deactivated500: Server error
Get authenticated user's profile.
Endpoint: GET /api/auth/me
Authentication: Required
Response: 200 OK
{
"_id": "65f1234567890abcdef12345",
"username": "john_doe",
"email": "john@example.com",
"emailVerified": true,
"emailNotifications": true,
"pushNotificationsEnabled": false,
"paymentReminderDays": 3,
"language": "en",
"role": "user",
"currency": "EUR",
"theme": "dark",
"monthlyRevenue": 0,
"annualRevenue": 0,
"createdAt": "2025-01-15T10:30:00.000Z"
}Change user password.
Endpoint: PUT /api/auth/password
Authentication: Required
Request Body:
{
"currentPassword": "OldPass123",
"newPassword": "NewSecurePass456"
}Validation:
newPassword: min 12 characters, must contain uppercase, lowercase, and number
Response: 200 OK
{
"message": "Password updated successfully"
}Change user email address.
Endpoint: PUT /api/auth/email
Authentication: Required
Request Body:
{
"email": "newemail@example.com"
}Response: 200 OK
{
"message": "Email updated successfully. Please check your inbox to verify your email.",
"email": "newemail@example.com",
"emailVerified": false
}Note: Email verification status is reset and verification email is sent.
Verify email address with token from email.
Endpoint: GET /api/auth/verify-email/:token
Authentication: None (public)
Response: 200 OK
{
"message": "Email verified successfully!"
}Errors:
400: Invalid or expired token
Request new verification email.
Endpoint: POST /api/auth/resend-verification
Authentication: Required
Response: 200 OK
{
"message": "Verification email sent successfully"
}Enable/disable email notifications.
Endpoint: PUT /api/auth/notifications
Authentication: Required
Request Body:
{
"emailNotifications": true
}Response: 200 OK
{
"message": "Notification preferences updated successfully",
"emailNotifications": true
}Set user's monthly and annual revenue.
Endpoint: PUT /api/auth/revenue
Authentication: Required
Request Body:
{
"monthlyRevenue": 5000,
"annualRevenue": 60000
}Response: 200 OK
{
"message": "Revenue updated successfully",
"monthlyRevenue": 5000,
"annualRevenue": 60000
}Update user preferences (theme, language, currency, notifications).
Endpoint: PUT /api/auth/preferences
Authentication: Required
Request Body:
{
"pushNotificationsEnabled": true,
"paymentReminderDays": 7,
"language": "fr",
"currency": "USD",
"theme": "dracula"
}Validation:
paymentReminderDays: 1, 3, or 7language: 'en' or 'fr'currency: 'EUR' or 'USD'theme: 'dark', 'light', 'dracula', 'nord', or 'solarized'
Response: 200 OK
{
"message": "Preferences updated successfully",
"pushNotificationsEnabled": true,
"paymentReminderDays": 7,
"language": "fr",
"currency": "USD",
"theme": "dracula"
}Get all subscriptions for authenticated user.
Endpoint: GET /api/subscriptions
Authentication: Required
Response: 200 OK
[
{
"_id": "65f1234567890abcdef12345",
"user": "65f1234567890abcdef12345",
"name": "Netflix",
"amount": 15.99,
"billingCycle": "monthly",
"category": "Entertainment",
"startDate": "2025-01-01T00:00:00.000Z",
"nextBillingDate": "2025-02-01T00:00:00.000Z",
"isActive": true,
"isTrial": false,
"isShared": false,
"notes": "Premium plan",
"iconFilename": "netflix_icon.png",
"notificationSent": false,
"createdAt": "2025-01-01T10:00:00.000Z",
"updatedAt": "2025-01-01T10:00:00.000Z"
}
]Get spending statistics for authenticated user.
Endpoint: GET /api/subscriptions/stats
Authentication: Required
Response: 200 OK
{
"totalMonthly": 125.50,
"totalMonthlyOnly": 85.50,
"totalAnnual": 480.00,
"totalYearly": 1506.00,
"count": 12,
"byCategory": {
"Entertainment": 45.99,
"Productivity": 35.00,
"Cloud Storage": 25.00,
"Other": 19.51
}
}Fields:
totalMonthly: Total monthly cost (including annual subscriptions / 12)totalMonthlyOnly: Only pure monthly subscriptionstotalAnnual: Total annual subscriptions costtotalYearly: Projected yearly cost (totalMonthly × 12)count: Number of active subscriptionsbyCategory: Breakdown by category
Add a new subscription.
Endpoint: POST /api/subscriptions
Authentication: Required
Request Body:
{
"name": "Spotify",
"amount": 9.99,
"billingCycle": "monthly",
"category": "Entertainment",
"startDate": "2025-01-15",
"nextBillingDate": "2025-02-15",
"isActive": true,
"isTrial": false,
"isShared": false,
"notes": "Individual plan"
}Validation:
name: required, non-emptyamount: required, positive numberbillingCycle: 'monthly' or 'annual'nextBillingDate: required, valid ISO8601 date
Optional fields:
category: stringstartDate: ISO8601 dateisTrial: booleantrialEndDate: ISO8601 dateisShared: booleansharedBy: number (people sharing)notes: stringiconFilename: string
Response: 201 Created
{
"_id": "65f1234567890abcdef67890",
"user": "65f1234567890abcdef12345",
"name": "Spotify",
"amount": 9.99,
"billingCycle": "monthly",
"category": "Entertainment",
"startDate": "2025-01-15T00:00:00.000Z",
"nextBillingDate": "2025-02-15T00:00:00.000Z",
"isActive": true,
"isTrial": false,
"isShared": false,
"notes": "Individual plan",
"notificationSent": false,
"createdAt": "2025-01-15T12:00:00.000Z",
"updatedAt": "2025-01-15T12:00:00.000Z"
}Update existing subscription.
Endpoint: PUT /api/subscriptions/:id
Authentication: Required (must own subscription)
Request Body: (partial update supported)
{
"amount": 12.99,
"nextBillingDate": "2025-03-01",
"notes": "Updated to premium plan"
}Response: 200 OK
{
"_id": "65f1234567890abcdef67890",
"user": "65f1234567890abcdef12345",
"name": "Spotify",
"amount": 12.99,
"billingCycle": "monthly",
"category": "Entertainment",
"nextBillingDate": "2025-03-01T00:00:00.000Z",
"notes": "Updated to premium plan",
...
}Errors:
404: Subscription not found401: Not authorized (not owner)
Delete a subscription.
Endpoint: DELETE /api/subscriptions/:id
Authentication: Required (must own subscription)
Response: 200 OK
{
"message": "Subscription removed"
}Errors:
404: Subscription not found401: Not authorized (not owner)
Note: Also deletes associated icon file if exists.
Get or generate calendar subscription token.
Endpoint: GET /api/subscriptions/calendar-token
Authentication: Required
Response: 200 OK
{
"token": "abc123def456",
"calendarUrl": "http://localhost:5071/api/subscriptions/calendar/abc123def456.ics"
}Usage: Subscribe to calendarUrl in your calendar app (Apple Calendar, Google Calendar, Outlook).
Get iCal feed for subscription with token.
Endpoint: GET /api/subscriptions/calendar/:token.ics
Authentication: None (public with token)
Response: 200 OK (text/calendar)
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Subly//Subscription Tracker//EN
X-WR-CALNAME:Subly - Subscription Payments
...
END:VCALENDAR
Errors:
404: Invalid calendar token
Download iCal file with authentication.
Endpoint: GET /api/subscriptions/calendar.ics
Authentication: Required
Response: 200 OK (text/calendar, attachment)
Downloads subly-subscriptions.ics file.
Check if invitation code is valid (public).
Endpoint: POST /api/invitations/validate
Authentication: None (public)
Rate Limit: 10 requests per hour per IP
Request Body:
{
"code": "ABC123"
}Response: 200 OK
{
"message": "Invitation code is valid"
}Errors:
404: Invalid invitation code400: Code expired or already used
Generate new invitation codes (admin only).
Endpoint: POST /api/invitations/generate
Authentication: Required (admin)
Request Body:
{
"count": 5,
"expiresInDays": 30,
"note": "Codes for beta testers"
}Validation:
count: 1-100 (default: 1)expiresInDays: 1-365 or null for no expiration (default: null)note: max 200 characters (optional)
Response: 201 Created
{
"message": "Successfully generated 5 invitation codes",
"invitations": [
{
"code": "ABC123",
"expiresAt": "2025-02-15T00:00:00.000Z",
"note": "Codes for beta testers",
"createdAt": "2025-01-15T10:00:00.000Z"
},
...
]
}List all invitation codes with stats (admin only).
Endpoint: GET /api/invitations
Authentication: Required (admin)
Response: 200 OK
{
"invitations": [
{
"_id": "65f1234567890abcdef12345",
"code": "ABC123",
"createdBy": {
"_id": "65f1234567890abcdef00000",
"username": "admin"
},
"isUsed": true,
"usedBy": {
"_id": "65f1234567890abcdef11111",
"username": "john_doe"
},
"usedAt": "2025-01-16T14:30:00.000Z",
"expiresAt": null,
"note": "Beta tester invite",
"createdAt": "2025-01-15T10:00:00.000Z"
}
],
"stats": {
"total": 20,
"active": 10,
"used": 8,
"expired": 2
}
}Revoke unused invitation code (admin only).
Endpoint: DELETE /api/invitations/:code
Authentication: Required (admin)
Response: 200 OK
{
"message": "Invitation code revoked successfully"
}Errors:
404: Code not found400: Cannot delete used codes
Get paginated list of users (admin only).
Endpoint: GET /api/admin/users
Authentication: Required (admin)
Query Parameters:
page: Page number (default: 1)limit: Items per page (default: 20)search: Search by username or emailincludeDeleted: Include soft-deleted users (default: false)
Example: GET /api/admin/users?page=1&limit=20&search=john&includeDeleted=false
Response: 200 OK
{
"users": [
{
"_id": "65f1234567890abcdef12345",
"username": "john_doe",
"email": "john@example.com",
"emailVerified": true,
"role": "user",
"isDeleted": false,
"createdAt": "2025-01-10T10:00:00.000Z",
"subscriptionCount": 8,
"totalMonthlySpend": 125.50
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 45,
"pages": 3
}
}Deactivate user account (admin only).
Endpoint: PUT /api/admin/users/:id/soft-delete
Authentication: Required (admin)
Response: 200 OK
{
"message": "User deactivated successfully",
"user": {
"_id": "65f1234567890abcdef12345",
"username": "john_doe"
}
}Errors:
404: User not found400: Cannot delete own account or already deleted
Restore soft-deleted user (admin only).
Endpoint: PUT /api/admin/users/:id/restore
Authentication: Required (admin)
Response: 200 OK
{
"message": "User restored successfully",
"user": {
"_id": "65f1234567890abcdef12345",
"username": "john_doe"
}
}Permanently delete user and all data (admin only).
Endpoint: DELETE /api/admin/users/:id
Authentication: Required (admin)
Response: 200 OK
{
"message": "User and all associated data permanently deleted"
}Warning: This action is irreversible. Deletes:
- User account
- All subscriptions
- All uploaded icons
Get dashboard statistics (admin only).
Endpoint: GET /api/admin/stats
Authentication: Required (admin)
Response: 200 OK
{
"totalUsers": 50,
"totalDeletedUsers": 3,
"totalSubscriptions": 380,
"activeSubscriptions": 320
}Get VAPID public key for browser push subscription.
Endpoint: GET /api/push/vapid-public-key
Authentication: None (public)
Response: 200 OK
{
"publicKey": "BNxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}Save push subscription for user.
Endpoint: POST /api/push/subscribe
Authentication: Required
Request Body:
{
"endpoint": "https://fcm.googleapis.com/fcm/send/...",
"keys": {
"p256dh": "BNxxxxxxxxxxxxxx...",
"auth": "xxxxxxxxxxxxxxxx"
}
}Response: 201 Created
{
"message": "Subscription saved successfully"
}Disable push notifications.
Endpoint: POST /api/push/unsubscribe
Authentication: Required
Request Body:
{
"endpoint": "https://fcm.googleapis.com/fcm/send/..."
}Response: 200 OK
{
"message": "Unsubscribed successfully"
}Check if user has active push subscription.
Endpoint: GET /api/push/status
Authentication: Required
Response: 200 OK
{
"subscribed": true
}400 Bad Request:
{
"message": "Validation error",
"errors": [
{
"field": "email",
"message": "Please enter a valid email address"
}
]
}401 Unauthorized:
{
"message": "Invalid credentials"
}403 Forbidden:
{
"message": "Access denied. Admin only."
}404 Not Found:
{
"message": "Resource not found"
}429 Too Many Requests:
{
"message": "Too many requests, please try again after 15 minutes"
}500 Internal Server Error:
{
"message": "Server error",
"error": "Detailed error message"
}| Code | Description |
|---|---|
| 400 | Bad Request - Invalid input |
| 401 | Unauthorized - Missing/invalid token |
| 403 | Forbidden - Insufficient permissions |
| 404 | Not Found - Resource doesn't exist |
| 429 | Too Many Requests - Rate limit exceeded |
| 500 | Internal Server Error - Server issue |
Rate limits protect the API from abuse.
| Endpoint | Limit | Window |
|---|---|---|
POST /api/auth/register |
3 requests | 1 hour |
POST /api/auth/login |
5 requests | 15 minutes |
POST /api/invitations/validate |
10 requests | 1 hour |
Responses include rate limit info:
X-RateLimit-Limit: 5
X-RateLimit-Remaining: 4
X-RateLimit-Reset: 1642185600
{
"message": "Too many authentication attempts, please try again after 15 minutes"
}Wait until X-RateLimit-Reset timestamp before retrying.
Login:
curl -X POST http://localhost:5071/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"SecurePass123"}'Get subscriptions:
curl -X GET http://localhost:5071/api/subscriptions \
-H "Authorization: Bearer YOUR_JWT_TOKEN"Create subscription:
curl -X POST http://localhost:5071/api/subscriptions \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Netflix",
"amount": 15.99,
"billingCycle": "monthly",
"nextBillingDate": "2025-02-01"
}'- Import collection or create requests manually
- Set base URL:
http://localhost:5071/api - Add authorization: Bearer Token
- Set headers:
Content-Type: application/json - Test endpoints
import axios from 'axios';
const API_URL = 'http://localhost:5071/api';
// Login
const login = async (username, password) => {
const response = await axios.post(`${API_URL}/auth/login`, {
username,
password
});
return response.data;
};
// Get subscriptions
const getSubscriptions = async (token) => {
const response = await axios.get(`${API_URL}/subscriptions`, {
headers: {
Authorization: `Bearer ${token}`
}
});
return response.data;
};
// Create subscription
const createSubscription = async (token, data) => {
const response = await axios.post(`${API_URL}/subscriptions`, data, {
headers: {
Authorization: `Bearer ${token}`
}
});
return response.data;
};- Installation Guide - Setup instructions
- Configuration Reference - Environment variables
- Development Guide - Local development
- Docker Guide - Docker deployment