Base URL:
/(no/apiprefix β routes mount directly) All responses follow:{ success, message, data }Auth via signed cookies:accessToken+refreshToken
| Code | Meaning |
|---|---|
| 200 | Success |
| 201 | Created |
| 202 | Accepted (pending action) |
| 400 | Validation / Bad Request |
| 401 | Unauthorized / Token expired |
| 403 | Forbidden (tier or permission) |
| 404 | Not Found |
| 409 | Conflict |
| 410 | Gone (match/chat closed) |
| 429 | Rate Limited |
| 503 | Service Unavailable |
| Tag | Meaning |
|---|---|
| π FREE | Any logged-in user |
| π₯ SILVER | Active Silver or Gold subscription |
| π₯ GOLD | Active Gold subscription only |
| π PUBLIC | No login required |
| Middleware | What it does |
|---|---|
validateBasicInfo |
Requires deviceId (32 chars), deviceSize, clientTimestamp in body or headers |
isLogin |
Verifies accessToken cookie. Returns 401 with code: refresh_auth_token if expired |
findLoginData |
Loads full user from DB using token payload |
isProfileExists |
Loads profile from DB. Returns 404 if no profile β redirect to /profile/setup |
checkPremiumStatus |
Auto-expires subscription if past expiresAt, cascades to next plan if available |
isPremiumUser() |
Blocks if no active subscription. isPremiumUser({ gold: true }) blocks non-gold |
isProfileBlocked |
Loads target profile by :username. Returns 404 if blocked or hidden |
rateLimiter(config) |
Redis-based sliding window rate limiter per route per user |
verifedMfaUser |
Requires MFA to be fully verified before accessing MFA manage routes |
| Error | Cause | Fix |
|---|---|---|
code: refresh_auth_token |
accessToken expired |
Call POST /auth/refresh/ |
code: relogin_required |
User deleted or token invalid | Force logout, redirect to login |
code: login_required |
No refreshToken cookie |
Redirect to login |
PROFILE_NOT_FOUND + next: create_profile |
Profile doesn't exist | Redirect to POST /profile/setup |
PREMIUM_REQUIRED |
Feature needs Silver/Gold | Show upgrade screen |
DISCOVER_BATCH_ACTIVE |
Batch already open | Call GET /discover/old first |
UPLOAD_NOT_FOUND |
S3 key not uploaded yet | Upload to uploadUrl first, then confirm |
429 on any route |
Rate limit hit | Backoff and retry |
Base:
/auth
Middleware: rateLimiter(10/60min) β signupValidation
Body:
{
"name": "Ayush",
"email": "ayush@example.com",
"username": "ayush_dev",
"password": "Secret@123",
"gender": "male",
"role": "developer",
"deviceId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"deviceSize": 1080,
"clientTimestamp": "2024-01-01T00:00:00.000Z"
}Success 200:
{
"success": true,
"message": "Verification Link Send Succesfull",
"data": { "name": "Ayush", "email": "ayush@example.com", "username": "ayush_dev" }
}Errors:
| Status | Cause |
|---|---|
| 400 | Validation failed (missing fields, weak password) |
| 409 | Email or username already exists |
Flow: Sends verification email β user clicks link β GET /auth/verify/?token=... β auto-login sets cookies.
Middleware: rateLimiter(20/60min)
Query: token β hex string from email link
Success: Sets accessToken, refreshToken, trustedSession cookies. Returns user info.
Errors:
| Status | Cause |
|---|---|
| 400 | Token missing or wrong length |
| 401 | Token expired or invalid |
Middleware: validateBasicInfo β rateLimiter(30/10min) β loginIdentifyValidation
Body:
{
"email": "ayush@example.com",
"deviceId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"deviceSize": 1080,
"clientTimestamp": "2024-01-01T00:00:00.000Z"
}Success 200:
{
"success": true,
"data": {
"riskLevel": "low",
"allowedMethod": ["password", "passkey"],
"stepUp": false
}
}Sets login_ctx cookie.
Errors:
| Status | Cause |
|---|---|
| 400 | Missing deviceId / clientTimestamp |
| 401 | Email not found |
| 403 | veryhigh risk + no 2FA β security upgrade required |
Middleware: validateBasicInfo β rateLimiter(30/10min) β full verify chain
Body:
{
"email": "ayush@example.com",
"password": "Secret@123",
"deviceId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"deviceSize": 1080,
"clientTimestamp": "2024-01-01T00:00:00.000Z"
}Success 200: Sets accessToken, refreshToken, trustedSession cookies.
{
"success": true,
"code": "LOGIN_SUCCESS",
"message": "User login successfully",
"data": { "name": "Ayush", "email": "ayush@example.com", "picture": "..." }
}Step-up 401 (2FA required):
{
"success": false,
"error": "STEP_UP_REQUIRED",
"action": "TRY_ANOTHER_VERIFICATION_METHOD"
}Sets twoFA_ctx cookie β proceed to /auth/verify-2fa/start/
Errors:
| Status | Cause |
|---|---|
| 401 | Wrong password / method not found |
| 403 | Step-up required |
Middleware: rateLimiter(10/60min) β twoFAValidation
Requires twoFA_ctx cookie.
Body:
{
"email": "ayush@example.com",
"loginMethod": "EMAIL",
"deviceId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"deviceSize": 1080,
"clientTimestamp": "2024-01-01T00:00:00.000Z"
}loginMethod options: EMAIL | TOTP | BACKUPCODE
Success 200:
{
"success": true,
"message": "Otp send Succesfull",
"route": "/auth/verify-2fa/confirm",
"requireCode": true
}Errors:
| Status | Cause |
|---|---|
| 401 | 2FA session expired, invalid method, or high-risk + backup code |
Middleware: rateLimiter(10/60min) β twoFAValidation
Requires twoFA_ctx cookie. Only works for EMAIL method.
Body: Same as start (email + deviceId + deviceSize + clientTimestamp)
Success 200: { "message": "Otp resend Succesfull" }
Middleware: rateLimiter(10/60min) β full 2FA verify chain
Requires twoFA_ctx cookie.
Body:
{
"email": "ayush@example.com",
"code": "123456",
"trustDevice": true,
"rememberDevice": true,
"deviceId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"deviceSize": 1080,
"clientTimestamp": "2024-01-01T00:00:00.000Z"
}Success 200: Sets all auth cookies. Returns user info.
Errors:
| Status | Cause |
|---|---|
| 401 | Wrong OTP / TOTP / backup code |
Middleware: validateBasicInfo β extractRefreshToken β rateLimiter(20/60min) β full refresh chain
Body: deviceId + deviceSize + clientTimestamp
Success 200: Rotates accessToken + refreshToken cookies.
{ "success": true, "action": "token_refreshed", "message": "Session refreshed successfully." }Force logout responses:
{ "success": false, "action": "logout-all", "message": "..." }
{ "success": false, "action": "logout", "message": "..." }Clears all cookies when security risk detected.
Errors:
| Status | Cause |
|---|---|
| 401 | Refresh token invalid / expired / revoked |
Middleware: validateBasicInfo β isLogin β findLoginData β rateLimiter(10/60min) β logout chain
Body: deviceId + deviceSize + clientTimestamp
Success 200: Clears accessToken + refreshToken cookies.
{ "success": true, "message": "You have been signed out", "id": "s_xxxxxxxx" }Middleware: Same as logout + rateLimiter(5/60min)
Success 200: Clears all cookies, invalidates all sessions.
{ "success": true, "message": "You've been signed out from all devices." }Middleware: isLogin β findLoginData β rateLimiter(20/60min)
Success 200:
{
"sessions": [
{
"id": "s_ab12cd34",
"device": { "name": "Chrome on Windows", "type": "desktop" },
"location": { "country": "IN", "city": "Mumbai" },
"login": { "method": "password", "mfa": false },
"trust": { "trusted": true, "level": "low" },
"activity": { "createdAt": "...", "lastActive": "..." },
"current": true
}
]
}Middleware: isLogin β findLoginData β rateLimiter(10/60min)
Params: :id β masked session id like s_ab12cd34
Success 200: { "revoked": true, "message": "Session has been signed out" }
Errors:
| Status | Cause |
|---|---|
| 400 | Invalid session id format |
| 401 | Cannot revoke current session |
| 404 | Session not found |
Middleware: isLogin β rateLimiter(150/5min)
Success 200:
{
"isLoggedIn": true,
"user": { "id": "...", "name": "Ayush", "email": "ayush@example.com" },
"profile": {
"exists": true,
"username": "ayush_dev",
"...": "full profile object"
}
}If no profile:
{
"profile": { "exists": false, "next": "create_profile", "route": "/profile/setup" }
}Middleware: validateBasicInfo β isLogin β findLoginData β rateLimiter(5/60min)
Initiates identity verification before password change. Sets verify_ctx cookie.
Middleware: validateBasicInfo β isLogin β findLoginData β rateLimiter(5/60min) β verifyVerifaction β verify chain
Body:
{
"password": "NewSecret@456",
"deviceId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"deviceSize": 1080,
"clientTimestamp": "2024-01-01T00:00:00.000Z"
}Success 200: { "message": "Password changed successfully" } β clears verify_ctx cookie.
Errors:
| Status | Cause |
|---|---|
| 401 | Same as old password / weak password / verify session expired |
Middleware: rateLimiter(10/5min)
Body: { "email": "ayush@example.com" }
Success 200: Always returns success (prevents email enumeration).
{ "success": true, "message": "we've sent password reset link successfully" }Middleware: rateLimiter(10/60min)
Validates reset token from email link.
Success 200: { "next": "set_new_password" }
Errors:
| Status | Cause |
|---|---|
| 400 | Token wrong length |
| 401 | Token expired or invalid |
Middleware: rateLimiter(5/60min)
Body: { "password": "NewSecret@456" }
Success 200: { "message": "Your password has been reset successfully. Please sign in again." }
Errors:
| Status | Cause |
|---|---|
| 401 | GET step not done first / unusual device detected / same as old password |
Middleware: isLogin β findLoginData β rateLimiter(40/5min)
Query: ?page=1&limit=20&types[]=login&types[]=stepup
Success 200:
{
"meta": { "page": 1, "limit": 20, "totalEvents": 45, "totalPages": 3 },
"events": [
{
"eventId": "...",
"eventType": "login",
"action": "login_failed",
"success": false,
"risk": "high",
"loginMethod": "password",
"mfaUsed": "none",
"location": { "country": "IN", "city": "Mumbai" },
"device": "Chrome on Windows",
"createdAt": "..."
}
]
}Middleware: isLogin β findLoginData β rateLimiter(40/5min)
Query: ?window=24h (options: 1h, 6h, 24h, 7d)
Success 200:
{
"risk": "high",
"reasons": ["multiple_failed_logins", "new_device"],
"actionRequired": true,
"recommendedActions": ["enable_2fa", "review_sessions"]
}Middleware: isLogin β findLoginData β rateLimiter(5/60min)
Starts identity verification before MFA management. Sets verify_ctx cookie.
Middleware: isLogin β findLoginData β rateLimiter(5/60min) β verifyVerifaction β verify chain
Completes identity verification. Sets rpat (restricted access token) for MFA manage routes.
Middleware: validateBasicInfo β isLogin β findLoginData β rateLimiter(60/10min) β verifedMfaUser
Success 200:
{
"mfa": {
"twoStepEnabled": true,
"secondSteps": {
"passkeys": { "enabled": true, "count": 2 },
"authenticator": { "enabled": true },
"googlePrompt": { "enabled": false, "devices": 0 },
"email": { "enabled": true, "emails": ["a***@gmail.com"] },
"backupCodes": { "enabled": true, "remaining": 8 }
},
"lastUpdated": "..."
}
}Enables 2FA on account.
Success 200: { "message": "TwoFA enabled successfully" }
| Method | Action |
|---|---|
| GET | List active backup codes |
| POST | Add backup codes |
| PUT | Renew (regenerate) backup codes |
| DELETE | Delete backup codes |
| Method | Action |
|---|---|
| GET | Get TOTP status |
| POST | Add TOTP authenticator |
| PATCH | Renew TOTP secret |
| DELETE | Remove TOTP |
| Method | Action |
|---|---|
| GET | List MFA emails |
| POST | Add new MFA email |
| DELETE | Remove MFA email |
Middleware: validateBasicInfo β isLogin β findLoginData β rateLimiter(60/10min) β verifedMfaUser
Creates a security code for login fallback.
| Method | Action |
|---|---|
| GET | List all passkeys |
| POST | Add new passkey (WebAuthn β 2 step: get challenge β submit response) |
| PATCH | Rename passkey (body: { id, name }) |
| DELETE | Remove passkey (body: { id }) |
POST flow:
- First call β
202with WebAuthnoptionschallenge - Sign challenge on device
- Second call with signed response β
201passkey added
| Method | Action |
|---|---|
| GET | List all trusted devices |
| DELETE | Revoke a trusted device |
Used for session approval login method (approve login from another device).
| Method | Action |
|---|---|
| GET | Get approval request info |
| POST | Approve or deny the login |
Base:
/profileBase middleware:rateLimiter(120/5min)on all routes All routes except/setupand/public/*require:isLoginβfindLoginDataβisProfileExists
Middleware: isLogin β findLoginData
Flow (2 steps):
Step 1 β Get upload URL (no key in body yet):
{
"displayName": "Ayush Dev",
"bio": "Full stack developer",
"role": "developer",
"techStack": ["Node.js", "React"],
"lookingFor": "collaboration",
"experienceYears": 3,
"phone": { "countryCode": "+91", "mobile": "9999999999" },
"fileName": "photo.jpg",
"fileType": "image/jpeg"
}Step 1 Response 200:
{
"code": "PHOTO_UPLOAD_PRESIGNED",
"data": { "uploadUrl": "https://s3...", "key": "temp/userId/xxx-photo.jpg", "fileUrl": "https://..." }
}Step 2 β Confirm profile (include key from step 1):
{
"displayName": "Ayush Dev",
"bio": "Full stack developer",
"role": "developer",
"techStack": ["Node.js", "React"],
"lookingFor": "collaboration",
"experienceYears": 3,
"phone": { "countryCode": "+91", "mobile": "9999999999" },
"key": "temp/userId/xxx-photo.jpg"
}Step 2 Response 201:
{
"data": {
"username": "ayush_dev",
"displayName": "Ayush Dev",
"bio": "Full stack developer",
"role": "developer",
"tech_stack": ["Node.js", "React"],
"looking_for": "collaboration",
"experience_years": 3,
"location": { "city": "Mumbai", "country": "IN" },
"visibility": "public",
"primaryPhoto": "https://...",
"profileScore": 40,
"createdAt": "..."
}
}Errors:
| Status | Code | Cause |
|---|---|---|
| 400 | β | Validation failed |
| 409 | UPLOAD_NOT_FOUND |
Key not uploaded to S3 yet β upload first then confirm |
| 409 | β | Profile already exists |
Success 200:
{
"data": {
"id": "...",
"username": "ayush_dev",
"displayName": "Ayush Dev",
"bio": "...",
"role": "developer",
"tech_stack": ["Node.js"],
"looking_for": "collaboration",
"experience_years": 3,
"location": { "city": "Mumbai", "country": "IN" },
"photos": [{ "id": "...", "url": "...", "isPrimary": false }, { "id": "none", "url": "...", "isPrimary": true }],
"phone": { "countryCode": "+91", "mobile": "9999999999" },
"visibility": "public",
"profileScore": 40,
"incognitoEnabled": false,
"badges": [],
"subscription": { "tier": "free", "isActive": false },
"stats": { "likes": 0, "views": 0 },
"createdAt": "...",
"updatedAt": "..."
}
}Allowed fields: displayName, bio, tech_stack, looking_for, experience_years, visibility, location, phone
Body (any subset):
{
"displayName": "New Name",
"bio": "Updated bio",
"tech_stack": ["Go", "Rust"],
"location": { "city": "Delhi", "country": "IN" }
}Success 200:
{
"message": "Profile updated successfully",
"data": { "username": "...", "displayName": "New Name", "...": "updated fields" },
"updatedFields": ["displayName", "bio"],
"version": 2
}Errors:
| Status | Cause |
|---|---|
| 400 | No valid fields / validation failed |
Middleware: rateLimiter(3/60min)
Schedules account for deletion (30-day grace period).
Success 200:
{
"message": "Account scheduled for deletion",
"data": { "gracePeriodDays": 30, "restoreUntil": "2024-02-01T00:00:00.000Z" }
}Errors:
| Status | Code | Cause |
|---|---|---|
| 409 | ALREADY_SCHEDULED |
Already scheduled for deletion |
Middleware: rateLimiter(3/60min)
Cancels scheduled deletion.
Success 200:
{ "message": "Account restored successfully", "data": { "status": "active", "restoredAt": "..." } }Errors:
| Status | Code | Cause |
|---|---|---|
| 400 | NOT_IN_DELETE_STATE |
Account not scheduled for deletion |
Success 200:
{
"data": {
"photos": [{ "id": "none", "url": "...", "isPrimary": true }],
"likes": 12,
"views": 45,
"matches": 3,
"profileScore": 40
}
}Toggles visibility between public and hidden.
Success 200:
{ "message": "Profile visibility updated", "data": { "visibility": "hidden" } }Middleware: checkPremiumStatus β isPremiumUser({ gold: true })
Toggles incognito mode (Gold only). When enabled, profile views are not recorded and profile is hidden from public view.
Success 200:
{ "message": "Incognito mode enabled", "data": { "incognito": true } }Errors:
| Status | Cause |
|---|---|
| 403 | Not a Gold member |
Middleware: checkPremiumStatus
Success 200:
{
"data": {
"total": 2,
"photos": [
{ "id": "abc123", "url": "https://...", "isPrimary": false, "createdAt": "..." },
{ "id": "none", "url": "https://...", "isPrimary": true, "createdAt": "..." }
]
}
}Middleware: checkPremiumStatus β rateLimiter(20/5min)
Photo limit: Free = 4, Silver/Gold = 8
Flow (2 steps):
Step 1 β Get upload URL:
{ "fileName": "photo.jpg", "fileType": "image/jpeg" }Step 1 Response 200:
{
"code": "PHOTO_UPLOAD_PRESIGNED",
"data": { "uploadUrl": "https://s3...", "key": "users/userId/xxx-photo.jpg", "fileUrl": "https://..." }
}Step 2 β Confirm upload:
{ "key": "users/userId/xxx-photo.jpg" }Step 2 Response 200:
{
"message": "Photo uploaded successfully",
"data": {
"photo": { "id": "...", "url": "https://...", "contentType": "image/jpeg", "size": 204800 },
"photosCount": 3,
"maxPhotosAllowed": 4
}
}Errors:
| Status | Code | Cause |
|---|---|---|
| 409 | PHOTO_LIMIT_REACHED |
Max photos reached for current plan |
| 409 | UPLOAD_NOT_FOUND |
Key not on S3 yet |
| 400 | β | Invalid file type or name |
Middleware: checkPremiumStatus β rateLimiter(10/5min)
Replace primary photo. Same 2-step flow as upload.
Step 1: Send fileName + fileType β get uploadUrl + key
Step 2: Send key β primary photo updated
Success 200:
{
"code": "PHOTO_PRIMARY_UPDATED",
"message": "Primary photo updated successfully",
"data": { "photo": { "id": "none", "url": "https://...", "isPrimary": true, "createdAt": "..." } }
}Errors:
| Status | Code | Cause |
|---|---|---|
| 409 | UPLOAD_NOT_FOUND |
Key not on S3 yet |
Middleware: rateLimiter(20/5min)
Params: :photoId β 24-char MongoDB ObjectId
Success 200:
{
"message": "Photo deleted successfully",
"data": { "photoId": "...", "deleted": true },
"meta": { "remainingPhotos": 2, "maxAllowed": 4, "tier": "free" }
}Errors:
| Status | Code | Cause |
|---|---|---|
| 400 | INVALID_OBJECT_ID |
photoId not 24 chars |
| 404 | PHOTO_NOT_FOUND |
Photo not in profile |
Middleware: checkPremiumStatus
See who viewed your profile (Gold only).
Query: ?limit=10&cursor=<ISO date>
Success 200:
{
"total": 45,
"views": [
{
"username": "dev_user",
"displayName": "Dev User",
"role": "developer",
"tech_stack": ["React"],
"photos": [{ "id": "none", "url": "...", "isPrimary": true }],
"location": { "city": "Delhi", "country": "IN" },
"badges": [],
"lastViewedAt": "...",
"viewCount": 3
}
],
"pagination": { "limit": 10, "hasMore": true, "nextCursor": "..." }
}Errors:
| Status | Cause |
|---|---|
| 403 | Not a Gold member |
Middleware: checkPremiumStatus β rateLimiter(20/5min)
Silver: see up to 5 unblurred + rest blurred. Gold: see all unblurred with pagination.
Query: ?limit=10&cursor=<ISO date> (cursor only works for Gold)
Success 200:
{
"swipes": [
{
"username": "dev_user",
"displayName": "Dev User",
"role": "developer",
"photos": [{ "url": "...", "isPrimary": true }],
"blurred": false,
"seenAt": "..."
},
{
"username": "hidden",
"displayName": "Someone rigth swipe you",
"photos": [{ "url": "blurred_url", "isPrimary": true }],
"blurred": true
}
],
"meta": {
"visible": 5,
"hidden": 3,
"upgradeHint": "Unlock Gold to see all rigth swipes",
"upgradeTier": "gold"
}
}Errors:
| Status | Cause |
|---|---|
| 403 | Not Silver or Gold |
Middleware: rateLimiter(100/5min) β optionalLogin β optionalProfile β isProfileBlocked
Public profile view. No login required but more info shown when logged in.
Success 200:
{
"data": {
"username": "ayush_dev",
"displayName": "Ayush Dev",
"bio": "...",
"role": "developer",
"tech_stack": ["Node.js"],
"looking_for": "collaboration",
"experience_years": 3,
"photos": [{ "id": "none", "url": "...", "isPrimary": true }],
"location": { "city": "Mumbai", "country": "IN" },
"badges": [],
"stats": { "likes": 12, "views": 45 }
}
}Errors:
| Status | Cause |
|---|---|
| 404 | Profile not found / hidden / blocked / incognito enabled |
Middleware: isLogin β findLoginData β isProfileExists β isProfileBlocked β rateLimiter(50/2min)
Like a public profile.
Success 200: { "message": "Profile liked successfully" }
Errors:
| Status | Cause |
|---|---|
| 404 | Profile not found or blocked |
Middleware: isLogin β findLoginData β isProfileExists β isProfileBlocked β rateLimiter(50/2min)
Unlike a public profile.
Success 200: { "message": "Profile unliked successfully" }
List all blocked users.
Query: ?limit=10&cursor=<ISO date>
Success 200:
{
"total": 3,
"blocked": [
{
"username": "bad_user",
"displayName": "Bad User",
"role": "developer",
"photos": [{ "id": "none", "url": "...", "isPrimary": true }],
"tech_stack": ["PHP"],
"location": { "city": "...", "country": "IN" },
"blockedAt": "..."
}
],
"pagination": { "limit": 10, "hasMore": false, "nextCursor": null }
}Middleware: rateLimiter(5/5min)
Success 200: { "message": "User blocked successfully" }
Errors:
| Status | Cause |
|---|---|
| 400 | Cannot block yourself |
| 404 | Profile not found |
| 409 | Already blocked |
Middleware: rateLimiter(5/5min)
Success 200: { "message": "User unblocked successfully" }
Errors:
| Status | Cause |
|---|---|
| 404 | Profile not found |
| 409 | User is not blocked |
Middleware: isProfileBlocked β rateLimiter(3/5min)
Body:
{ "reason": "spam", "description": "This user is spamming" }Success 200: { "message": "Profile reported successfully" }
List profiles you have reported.
Success 200: { "reports": [...] }
Get current incoming ringtone.
Success 200: { "data": { "url": "...", "isDefault": true } }
Middleware: checkPremiumStatus β isPremiumUser() β rateLimiter(20/5min)
Update incoming ringtone (Silver or Gold).
Middleware: checkPremiumStatus β isPremiumUser() β rateLimiter(20/5min)
Reset incoming ringtone to default.
Get current ringback tone.
Middleware: checkPremiumStatus β isPremiumUser({ gold: true }) β rateLimiter(10/5min)
Update ringback tone (Gold only).
Middleware: checkPremiumStatus β isPremiumUser({ gold: true }) β rateLimiter(10/5min)
Reset ringback tone to default.
Base:
/discoverBase middleware (all routes):isLoginβfindLoginDataβisProfileExistsβrateLimiter(50/2min)βcheckPremiumStatus
Get a new batch of profiles to swipe.
Free limit: 20 profiles/day Silver limit: 80 profiles/day Gold: Unlimited + geo-based matching
Query (Gold only): ?maxDistance=50000&cursor=<ISO date>&limit=10
Success 200:
{
"profiles": [
{
"username": "dev_user",
"displayName": "Dev User",
"role": "developer",
"tech_stack": ["React", "Node.js"],
"location": { "city": "Mumbai", "country": "IN" },
"photos": [{ "id": "none", "url": "...", "isPrimary": true }],
"score": 0.87,
"badges": []
}
],
"pagination": { "hasMore": true, "nextCursor": "..." },
"meta": { "tier": "free", "remainingToday": 15, "unlimited": false }
}Errors:
| Status | Code | Cause |
|---|---|---|
| 409 | DISCOVER_BATCH_ACTIVE |
Active batch exists β call GET /discover/old first |
| 429 | PROFILE_LIMIT_REACHED |
Daily limit hit β upgrade to see more |
Get current active batch (already fetched profiles not yet swiped).
Success 200:
{
"message": "Old discover profiles fetched successfully.",
"profiles": [{ "username": "...", "...": "..." }]
}Errors:
| Status | Code | Cause |
|---|---|---|
| 403 | DISCOVER_BATCH_FORBIDDEN |
IP mismatch β session from different device |
| 404 | DISCOVER_BATCH_NOT_FOUND |
No active batch β call GET /discover/ first |
Middleware: isProfileBlocked β swipeProfile
Left swipe (pass) a profile.
Success 200:
{
"message": "Profile passed",
"data": { "username": "dev_user", "action": "pass", "passed": true },
"meta": { "tier": "free", "unlimited": false }
}Middleware: isProfileBlocked β swipeProfile
Right swipe (like) a profile.
Free limit: 15 right swipes/day Silver limit: 50 right swipes/day Gold: Unlimited
Success 200 (no match):
{
"message": "Profile Liked",
"data": { "username": "dev_user", "action": "like", "liked": true, "match": false },
"meta": { "tier": "free", "unlimited": false }
}Success 201 (match!):
{
"message": "It's a match",
"data": { "username": "dev_user", "action": "like", "liked": true, "match": true, "matchId": "..." },
"meta": { "tier": "free", "unlimited": false, "next": "open_chat", "route": "/chat/matchId" }
}Errors:
| Status | Code | Cause |
|---|---|---|
| 429 | RIGTH_SWIPE_LIMIT_REACHED |
Daily right swipe limit hit |
See who right-swiped your profile.
Silver: first 5 unblurred, rest blurred. Gold: all unblurred with cursor pagination.
Query (Gold): ?limit=10&cursor=<ISO date>
Success 200: (same shape as GET /profile/likes)
Errors:
| Status | Cause |
|---|---|
| 403 | Not Silver or Gold |
Middleware: rateLimiter(15/5min)
Undo last left swipe. Gold only.
Daily limit: Standard Gold = 10 rewinds/day, Lifetime Gold = higher limit
Success 200:
{
"message": "Rewind successful",
"data": {
"action": "rewind",
"restoredProfile": {
"username": "dev_user",
"displayName": "Dev User",
"role": "developer",
"photos": [{ "id": "none", "url": "...", "isPrimary": true }],
"tech_stack": ["React"],
"location": { "city": "Mumbai", "country": "IN" },
"badges": []
}
},
"meta": { "tier": "gold", "rewindRemainingToday": 9 }
}Errors:
| Status | Code | Cause |
|---|---|---|
| 403 | PREMIUM_REQUIRED |
Not Gold |
| 409 | NOTHING_TO_REWIND |
No previous left swipe to undo |
| 429 | REWIND_LIMIT_REACHED |
Daily rewind limit hit |
Middleware: rateLimiter(5/10min) β checkPacksStatus
Boost your profile visibility temporarily.
Success 200: { "message": "Profile boosted successfully" }
Errors:
| Status | Cause |
|---|---|
| 403 | No active boost pack |
Base:
/matchBase middleware (all routes):isLoginβfindLoginDataβisProfileExistsβrateLimiter(80/2min)βcheckPremiumStatus
Get all your matches with pagination.
Query: ?limit=10&cursor=<ISO date>
Success 200:
{
"matches": [
{
"matchId": "...",
"status": "active",
"user": {
"username": "dev_user",
"displayName": "Dev User",
"role": "developer",
"photos": [{ "id": "none", "url": "...", "isPrimary": true }],
"badges": []
},
"createdAt": "...",
"lastMessageAt": "..."
}
],
"pagination": { "hasMore": true, "nextCursor": "..." }
}Errors:
| Status | Cause |
|---|---|
| 400 | Invalid cursor format |
Middleware: rateLimiter(40/2min)
Get a specific match detail.
Params: :matchId β 24-char MongoDB ObjectId
Success 200:
{
"data": {
"matchId": "...",
"status": "active",
"createdAt": "...",
"lastMessageAt": "...",
"user": {
"username": "dev_user",
"displayName": "Dev User",
"bio": "...",
"role": "developer",
"tech_stack": ["React"],
"photos": [{ "id": "none", "url": "...", "isPrimary": true }],
"location": { "city": "Mumbai", "country": "IN" },
"badges": []
}
}
}Errors:
| Status | Code | Cause |
|---|---|---|
| 400 | INVALID_MATCH_ID |
matchId not 24 chars |
| 403 | MATCH_FORBIDDEN |
You are not part of this match |
| 404 | MATCH_NOT_FOUND |
Match does not exist |
| 410 | MATCH_CLOSED |
Match unmatched or profile hidden/blocked |
410 Unmatched response (Gold sees who unmatched):
{
"code": "MATCH_CLOSED",
"data": {
"status": "unmatched",
"unmatchedAt": "...",
"isSelf": false,
"unmatchedBy": {
"displayName": "Dev User",
"username": "dev_user",
"picture": { "url": "..." }
}
}
}Non-gold users see
"unmatchedBy": "hidden"
Middleware: rateLimiter(10/10min)
Unmatch / revoke a match.
Success 200: { "message": "Match revoked successfully" }
Errors:
| Status | Code | Cause |
|---|---|---|
| 400 | INVALID_MATCH_ID |
matchId not 24 chars |
| 404 | MATCH_NOT_FOUND |
Match does not exist |
Get all deactivated (unmatched) matches.
Success 200:
{
"matches": [
{
"matchId": "...",
"status": "unmatched",
"user": { "username": "...", "displayName": "..." },
"unmatchedAt": "..."
}
]
}Middleware: rateLimiter(10/10min) β isPremiumUser()
Restore a previously unmatched match. Requires Silver or Gold.
Success 200: { "message": "Match restored successfully" }
Errors:
| Status | Code | Cause |
|---|---|---|
| 403 | PREMIUM_REQUIRED |
Not Silver or Gold |
| 404 | MATCH_NOT_FOUND |
Match does not exist |
Base:
/chatBase middleware (all routes):isLoginβfindLoginDataβisProfileExistsβrateLimiter(50/2min)
Get all chats (inbox).
Query: ?limit=10&cursor=<ISO date>
Success 200:
{
"message": "All chats fetched successfull",
"chats": [
{
"chatId": "...",
"matchId": "...",
"opponent": {
"username": "dev_user",
"displayName": "Dev User",
"primaryPhoto": { "url": "..." }
},
"lastMessage": {
"text": "Hey!",
"type": "text",
"sender": "...",
"messageId": "...",
"sentAt": "..."
},
"lastMessageAt": "...",
"unreadCount": 2,
"isPinned": false,
"isMuted": false,
"isArchived": false
}
],
"pagination": { "hasMore": true, "nextCursor": "..." }
}Get specific chat info.
Params: :chatId β 24-char MongoDB ObjectId
Success 200:
{
"message": "Chat fetched successfully",
"data": {
"chatId": "...",
"matchId": "...",
"opponent": {
"userId": "...",
"username": "dev_user",
"displayName": "Dev User",
"primaryPhoto": { "url": "..." },
"lastSeen": "..."
},
"settings": {
"isPinned": false,
"isMuted": false,
"isArchived": false,
"unreadCount": 0
},
"lastMessage": { "text": "Hey!", "type": "text", "sentAt": "..." },
"lastMessageAt": "...",
"permissions": {
"canSendMessage": true,
"canCall": true,
"canVideoCall": true
}
}
}Errors:
| Status | Code | Cause |
|---|---|---|
| 400 | INVALID_CHAT_ID |
chatId not valid ObjectId |
| 403 | CHAT_FORBIDDEN |
You are not part of this chat |
| 404 | CHAT_NOT_FOUND |
Chat does not exist |
| 409 | CHAT_CLOSED |
Chat is no longer active |
Middleware: validateChatAccess
Get messages for a chat with cursor pagination.
Query: ?limit=10&cursor=<ISO date>
Success 200:
{
"message": "All messages fetched successfull",
"data": {
"chatId": "...",
"messages": [
{
"messageId": "...",
"type": "text",
"text": "Hey!",
"senderId": "...",
"createdAt": "...",
"status": "read"
}
]
},
"pagination": { "hasMore": true, "nextCursor": "..." }
}Automatically marks unread messages as read on fetch.
Get a single message by ID.
Success 200:
{
"message": "Message fetched successfully",
"data": {
"chatId": "...",
"messageId": "...",
"type": "text",
"text": "Hey!",
"senderId": "...",
"createdAt": "..."
}
}Errors:
| Status | Code | Cause |
|---|---|---|
| 404 | MESSAGE_NOT_FOUND |
Message deleted or not accessible |
| 404 | CHAT_NOT_FOUND |
Chat no longer exists |
Middleware: validateChatAccess
Clear all messages for yourself only (soft delete β sets deletedAt timestamp).
Success 200:
{
"message": "Chat cleared successfully",
"data": { "chatId": "...", "clearedAt": "..." }
}Middleware: validateChatAccess
Hard delete all messages for everyone. Gold or Admin only.
Success 200:
{
"message": "All messages deleted successfully",
"data": { "chatId": "...", "deletedCount": 42 }
}Errors:
| Status | Code | Cause |
|---|---|---|
| 403 | FORBIDDEN |
Not Gold or Admin |
Middleware: validateChatAccess
Toggle pin on a chat.
Success 200: { "message": "Chat pin toggled" }
Middleware: validateChatAccess
Toggle mute on a chat.
Success 200: { "message": "Chat mute toggled" }
Middleware: validateChatAccess
Toggle archive on a chat.
Success 200: { "message": "Chat archive toggled" }
Upload media for chat (image, audio, video).
Body: multipart/form-data with file
Success 200:
{
"data": { "url": "https://s3...", "key": "...", "type": "image/jpeg", "size": 204800 }
}Sync chat metadata (unread counts, last seen, etc).
Success 200: { "message": "Sync successful" }
Base:
/callBase middleware (all routes):isLoginβfindLoginDataβisProfileExistsβrateLimiter(50/2min)
Get call history with pagination.
Query: ?limit=20&cursor=<ISO date>
Success 200:
{
"message": "Call history fetched successfully",
"calls": [
{
"callId": "...",
"chatId": "...",
"type": "audio",
"status": "ended",
"isActive": false,
"direction": "outgoing",
"with": {
"userId": "...",
"name": "Dev User",
"photo": "https://..."
},
"timestamps": { "startedAt": "...", "endedAt": "..." },
"duration": 120,
"endReason": "normal"
}
],
"pagination": { "hasMore": true, "nextCursor": "..." }
}Get specific call detail.
Success 200:
{
"call": {
"callId": "...",
"chatId": "...",
"type": "video",
"status": "ongoing",
"isActive": true,
"direction": "incoming",
"with": { "userId": "...", "name": "Dev User", "photo": "..." },
"participants": {
"caller": { "userId": "...", "name": "...", "photo": "..." },
"receiver": { "userId": "...", "name": "...", "photo": "..." }
},
"media": { "audio": true, "video": true },
"timestamps": { "createdAt": "...", "startedAt": "...", "endedAt": null },
"duration": null,
"endReason": null,
"connection": {
"canAccept": false,
"canReject": false,
"canReconnect": true,
"resumeWebRTC": true,
"gracePeriod": 15
}
}
}Errors:
| Status | Code | Cause |
|---|---|---|
| 404 | CALL_NOT_FOUND |
Call does not exist or not your call |
Delete all call logs (only ended/missed calls, not active ones).
Success 200:
{ "success": true, "message": "Call history cleared successfully" }Delete a specific call log.
Success 200:
{
"success": true,
"message": "Call deleted successfully",
"data": { "callId": "..." }
}Errors:
| Status | Code | Cause |
|---|---|---|
| 404 | CALL_NOT_FOUND |
Call not found or still active |
Base:
/subscriptionBase middleware (all routes except webhooks):isLoginβfindLoginDataβisProfileExistsβcheckPremiumStatusβrateLimiter(50/2min)Webhook routes (/webhook/*) skip auth β verified via Cashfree signature
Get all available subscription plans with current user context.
Success 200:
{
"currentPlan": {
"id": "free",
"label": "Free",
"isPaid": false,
"isTrial": false,
"startedAt": null,
"expiresAt": null,
"isLifetime": false
},
"popularPlan": "silver",
"plans": [
{
"id": "free",
"label": "Free",
"price": 0,
"currency": "INR",
"duration": null,
"isDefault": true,
"popular": false,
"isCurrent": true,
"canUpgrade": false,
"canDowngrade": false,
"requiresPayment": false,
"features": {}
},
{
"id": "silver",
"label": "Silver",
"price": 199,
"currency": "INR",
"duration": 30,
"isDefault": false,
"popular": true,
"isCurrent": false,
"canUpgrade": true,
"canDowngrade": false,
"requiresPayment": true,
"features": {}
},
{
"id": "gold",
"label": "Gold",
"price": 399,
"currency": "INR",
"duration": 30,
"isDefault": false,
"popular": false,
"isCurrent": false,
"canUpgrade": true,
"canDowngrade": false,
"requiresPayment": true,
"features": {}
},
{
"id": "gold_trial",
"label": "Gold (30 Days Trial)",
"price": 0,
"currency": "INR",
"duration": 30,
"isTrial": true,
"requiresPayment": false,
"features": {}
}
]
}
gold_trialonly appears if user has never used a trial before and has no active Gold.
Get current subscription status of logged-in user.
Success 200:
{
"tier": "silver",
"isActive": true,
"isLifetime": false,
"isTrial": false,
"since": "2024-01-01T00:00:00.000Z",
"expiresAt": "2024-02-01T00:00:00.000Z"
}Middleware: validateBasicInfo
Get subscription purchase history.
Success 200:
{
"history": [
{
"id": "...",
"action": "PURCHASE",
"fromPlan": "free",
"toPlan": "silver",
"isTrial": false,
"createdAt": "..."
}
]
}Middleware: validateBasicInfo β rateLimiter(10/5min) β validatePlan β initlizeGateway β validateCoupon β finalizeAmount β createOrder β sendPayment
Initiate a subscription purchase via Cashfree.
Body:
{
"planId": "silver",
"coupon": "SAVE50",
"method": "upi",
"deviceId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"deviceSize": 1080,
"clientTimestamp": "2024-01-01T00:00:00.000Z"
}planId options: silver | gold
Success 200:
{
"orderId": "...",
"gateway": "cashfree",
"method": "upi",
"payment": {
"orderId": "cf_order_xxx",
"paymentSessionId": "session_xxx"
},
"expiresIn": 600
}Errors:
| Status | Code | Cause |
|---|---|---|
| 400 | PLAN_NOT_FOUND |
Invalid planId |
| 400 | COUPON_INVALID |
Coupon expired / not applicable |
| 400 | COUPON_LIMIT_REACHED |
Coupon usage limit exceeded |
Middleware: validateBasicInfo β rateLimiter(5/60min) β activateTrial β initlizeGateway β createAutopay β sendPayment
Activate Gold 30-day free trial (sets up autopay mandate).
Body:
{
"deviceId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"deviceSize": 1080,
"clientTimestamp": "2024-01-01T00:00:00.000Z"
}Success 200:
{
"orderId": "...",
"gateway": "cashfree",
"payment": {
"orderId": "sub_xxx",
"subscriptionSessionId": "sub_session_xxx"
},
"expiresIn": 600
}Errors:
| Status | Cause |
|---|---|
| 409 | Trial already used or active Gold exists |
Middleware: rateLimiter(5/60min)
Request refund for current subscription.
Success 200: { "message": "Refund initiated successfully" }
Errors:
| Status | Cause |
|---|---|
| 400 | No active subscription to refund |
| 409 | Refund already requested |
Middleware: rateLimiter(5/60min)
Request refund for autopay subscription.
Success 200: { "message": "Autopay refund initiated successfully" }
Middleware: rateLimiter(10/60min)
Pause active autopay mandate.
Success 200: { "message": "Autopay paused successfully" }
Errors:
| Status | Cause |
|---|---|
| 404 | No active autopay found |
Middleware: rateLimiter(10/60min)
Resume paused autopay mandate.
Success 200: { "message": "Autopay resumed successfully" }
Middleware: rateLimiter(3/60min)
Cancel autopay mandate permanently.
Success 200: { "message": "Autopay cancelled successfully" }
Errors:
| Status | Cause |
|---|---|
| 404 | No active autopay found |
Middleware: validateSigntaure β validateBody β validateOrder β handlePaymentCoupon β handlePaymentSuccess
Cashfree payment webhook. Verifies signature, activates subscription on success.
Do NOT call this manually. Cashfree calls this automatically after payment.
Middleware: validateSigntaure β validateSubscriptionBody β handleAutoPayWebhook β handleAutoPaySuccess
Cashfree autopay webhook. Handles subscription renewals.
Middleware: validateSigntaure β validateRefundBody β handleRefundWebhook
Cashfree refund webhook for one-time payments.
Middleware: validateSigntaure β validateRefundBody β handleRefundAutoPayWebhook
Cashfree refund webhook for autopay subscriptions.
Base:
/paymentBase middleware (all routes):isLoginβfindLoginDataβisProfileExistsβrateLimiter(100/2min)
Get all available active coupons for the current user.
Success 200:
{
"coupons": [
{
"code": "SAVE50",
"discount": { "type": "flat", "value": 50 },
"applicablePlans": ["silver", "gold"],
"expiresAt": "2024-12-31T00:00:00.000Z"
}
]
}Validate a coupon code before checkout.
Body:
{ "code": "SAVE50", "planId": "silver" }Success 200:
{
"valid": true,
"code": "SAVE50",
"discount": { "type": "flat", "value": 50 },
"finalAmount": 149
}Errors:
| Status | Cause |
|---|---|
| 400 | Coupon not found / expired / not applicable to plan |
| 409 | Coupon usage limit reached |
Base:
/push
Middleware: isLogin β findLoginData β isProfileExists β rateLimiter(20/5min)
Subscribe device for web push notifications.
Body:
{
"endpoint": "https://fcm.googleapis.com/...",
"keys": {
"p256dh": "...",
"auth": "..."
}
}Success 200: { "message": "Push subscription saved successfully" }
Errors:
| Status | Cause |
|---|---|
| 400 | Invalid push subscription object |
Middleware: isLogin β findLoginData β isProfileExists β rateLimiter(20/5min)
Unsubscribe device from web push notifications.
Body:
{ "endpoint": "https://fcm.googleapis.com/..." }Success 200: { "message": "Push subscription removed successfully" }
Errors:
| Status | Cause |
|---|---|
| 404 | Subscription not found |
Base:
/system
Middleware: rateLimiter(60/1min)
Check health of all backend services.
Success 200:
{
"success": true,
"status": "ok",
"services": {
"db": "ok",
"redis": "ok",
"mail": "ok",
"push": "ok"
},
"timestamp": "2024-01-01T00:00:00.000Z"
}Degraded 503:
{
"success": true,
"status": "degraded",
"services": {
"db": "ok",
"redis": "down",
"mail": "ok",
"push": "disabled"
},
"timestamp": "..."
}Returns
503if any service is down/disabled.
| Method | Route | Auth | Tier |
|---|---|---|---|
| POST | /auth/signup/ |
β | π |
| GET | /auth/verify/ |
β | π |
| POST | /auth/login/identify/ |
β | π |
| POST | /auth/login/confirm/ |
β | π |
| POST | /auth/verify-2fa/start/ |
β | π |
| POST | /auth/verify-2fa/resend/ |
β | π |
| POST | /auth/verify-2fa/confirm/ |
β | π |
| POST | /auth/refresh/ |
β | π |
| POST | /auth/forgot-password/ |
β | π |
| GET | /auth/reset-password/:token/ |
β | π |
| POST | /auth/reset-password/:token/ |
β | π |
| POST | /auth/logout/ |
β | π |
| POST | /auth/logout-all/ |
β | π |
| GET | /auth/session/ |
β | π |
| POST | /auth/session/revoke/:id/ |
β | π |
| GET | /auth/me |
β | π |
| POST | /auth/change-password/start/ |
β | π |
| POST | /auth/change-password/confirm/ |
β | π |
| GET | /auth/account/security-events/ |
β | π |
| GET | /auth/account/active-risks/ |
β | π |
| GET/POST | /auth/account/approve-login/:id |
β | π |
| POST | /auth/mfa/start/ |
β | π |
| POST | /auth/mfa/verify/ |
β | π |
| GET/POST | /auth/mfa/manage/ |
β MFA | π |
| GET/POST/PUT/DELETE | /auth/mfa/manage/backupcode/ |
β MFA | π |
| GET/POST/PATCH/DELETE | /auth/mfa/manage/totp/ |
β MFA | π |
| GET/POST/DELETE | /auth/mfa/manage/email/ |
β MFA | π |
| POST | /auth/mfa/manage/email/verify/ |
β MFA | π |
| POST | /auth/mfa/manage/email/resend/ |
β MFA | π |
| POST | /auth/manage/securitycode/ |
β MFA | π |
| GET/POST/PATCH/DELETE | /auth/manage/passkey/ |
β MFA | π |
| GET/DELETE | /auth/manage/trusted-devices/ |
β MFA | π |
| POST | /profile/setup |
β | π |
| GET/PATCH/DELETE | /profile/me |
β | π |
| POST | /profile/restore |
β | π |
| GET | /profile/stats |
β | π |
| PATCH | /profile/visibility |
β | π |
| PATCH | /profile/incognito |
β | π₯ |
| GET/POST/PATCH | /profile/photo |
β | π |
| DELETE | /profile/photo/:photoId |
β | π |
| GET | /profile/views |
β | π₯ |
| GET | /profile/likes |
β | π₯/π₯ |
| GET | /profile/public/:username |
β | π |
| POST | /profile/public/:username/like |
β | π |
| DELETE | /profile/public/:username/like |
β | π |
| GET | /profile/block |
β | π |
| POST/DELETE | /profile/block/:username |
β | π |
| POST | /profile/report/:username |
β | π |
| GET | /profile/report/ |
β | π |
| GET/PATCH/DELETE | /profile/ringtone/incoming/ |
β | π/π₯ |
| GET/PATCH/DELETE | /profile/ringtone/ringback/ |
β | π/π₯ |
| GET | /discover/ |
β | π |
| GET | /discover/old |
β | π |
| POST | /discover/pass/:username |
β | π |
| POST | /discover/like/:username |
β | π |
| GET | /discover/likes |
β | π₯/π₯ |
| POST | /discover/rewind/ |
β | π₯ |
| POST | /discover/boost/ |
β | π₯/π₯ |
| GET | /match/ |
β | π |
| GET | /match/restore/ |
β | π |
| GET/DELETE | /match/:matchId |
β | π |
| POST | /match/restore/:matchId |
β | π₯/π₯ |
| GET | /chat/ |
β | π |
| GET | /chat/message/:messageId |
β | π |
| POST | /chat/sync |
β | π |
| POST | /chat/upload |
β | π |
| GET | /chat/:chatId |
β | π |
| DELETE | /chat/:chatId |
β | π₯ |
| DELETE | /chat/:chatId/clear |
β | π |
| GET | /chat/:chatId/messages |
β | π |
| PATCH | /chat/:chatId/pin |
β | π |
| PATCH | /chat/:chatId/mute |
β | π |
| PATCH | /chat/:chatId/archive |
β | π |
| GET/DELETE | /call/ |
β | π |
| GET/DELETE | /call/:callId |
β | π |
| GET | /subscription/plans |
β | π |
| GET | /subscription/subscription-status |
β | π |
| GET | /subscription/history |
β | π |
| POST | /subscription/checkout |
β | π |
| POST | /subscription/activate-trial |
β | π |
| POST | /subscription/refund |
β | π |
| POST | /subscription/refund-autopay |
β | π |
| POST | /subscription/pause-autopay |
β | π |
| POST | /subscription/resume-autopay |
β | π |
| POST | /subscription/cancel-autopay |
β | π |
| POST | /subscription/webhook/payment |
β | π |
| POST | /subscription/webhook/autopay |
β | π |
| POST | /subscription/webhook/refund/payment |
β | π |
| POST | /subscription/webhook/refund/autopay |
β | π |
| GET | /payment/coupons |
β | π |
| POST | /payment/coupon |
β | π |
| POST | /push/subscribe |
β | π |
| DELETE | /push/unsubscribe |
β | π |
| GET | /system/health/ |
β | π |