Zaptam Routes Reference
Complete map of all API endpoints with methods, middleware, and request/response formats.
Base URL
Production: https://api.zaptam.com
Development: http://localhost:3000All endpoints prefixed with /api/
Quick Reference
| Group | Endpoints | Auth Required | Description |
|---|---|---|---|
| Auth | 9 | Partial | Registration, login, OTP |
| Users | 6 | ✓ | Profile & settings |
| Discover | 3 | ✓ | Browse profiles |
| Matches | 2 | ✓ | View & manage matches |
| Messages | 5 | ✓ | Conversations & chat |
| Wallet | 5 | ✓ | Balance & transactions |
| Verification | 4 | ✓ | Submit & check status |
| Reports | 3 | ✓ | Submit reports |
| Admin | 15 | ✓ (CURATOR+) | Admin dashboard |
| Total | 52 |
Authentication Routes
Base: /api/auth
POST /api/auth/apply
Create account application and send OTP.
Rate Limit: authLimiter (10 requests/15 min)
Request:
// multipart/form-data
{
phoneNumber: string; // Required, +268XXXXXXXX format
gender: 'MALE' | 'FEMALE';
email?: string;
name?: string;
occupation?: string; // Required for MALE
intro?: string; // For FEMALE
netWorth?: '50k-100k' | '100k-500k' | '500k-1m' | '1m-5m' | '5m+'; // Required for MALE
photos?: File[]; // Required for FEMALE, max 5
}Response:
{
"success": true,
"message": "Verification code sent",
"data": {
"expiresAt": "2024-01-01T12:10:00Z"
}
}POST /api/auth/verify-phone
Request OTP for existing user.
Rate Limit: otpLimiter (3 requests/1 min)
// Request
{ phoneNumber: string }
// Response
{ success: true, message: "Verification code sent", data: { expiresAt: string } }POST /api/auth/confirm-phone
Verify OTP code.
Rate Limit: authLimiter
// Request
{
phoneNumber: string;
code: string; // 6 digits
}
// Response
{ success: true, message: "Phone verified" }POST /api/auth/complete-profile
Complete registration after OTP verification.
Rate Limit: authLimiter
// Request
{
phoneNumber: string;
password: string; // Min 8 chars
bio?: string; // Max 500 chars
dateOfBirth?: string; // ISO date
}
// Response
{
"success": true,
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "550e8400-e29b-41d4-a716-446655440000"
}
}POST /api/auth/login
Authenticate with credentials.
Rate Limit: authLimiter
// Request
{
phoneNumber: string;
password: string;
}
// Response
{
"success": true,
"data": {
"accessToken": string,
"refreshToken": string
}
}POST /api/auth/refresh
Rotate access token.
// Request
{ refreshToken: string }
// Response
{ success: true, data: { accessToken: string, refreshToken: string } }POST /api/auth/logout
Revoke tokens.
Auth: Required
// Request (optional)
{ refreshToken?: string } // If omitted, revokes all tokens
// Response
{ success: true, message: "Logged out" }POST /api/auth/forgot-password
Request password reset OTP.
Rate Limit: otpLimiter
// Request
{ phoneNumber: string }
// Response
{ success: true, message: "Reset code sent" }POST /api/auth/reset-password
Reset password with OTP.
Rate Limit: authLimiter
// Request
{
phoneNumber: string;
code: string;
newPassword: string; // Min 8 chars
}
// Response
{ success: true, message: "Password reset successful" }User Routes
Base: /api/usersAuth: All routes require authentication
GET /api/users/me
Get current user profile.
// Response
{
"success": true,
"data": {
"id": "uuid",
"phoneNumber": "+268XXXXXXXX",
"email": "user@example.com",
"alias": "mysteryman",
"bio": "Just looking...",
"gender": "MALE",
"status": "ACTIVE",
"trustScore": 75,
"verificationLevel": "IDENTITY",
"settings": {
"aliasMode": true,
"showOnlineStatus": false,
"regionMasking": true,
"photoBlurLevel": 100,
"disappearingMsgs": null
},
"photos": [...]
}
}PATCH /api/users/me
Update profile.
Middleware: requireActiveStatus
// Request
{
alias?: string; // 3-30 chars
bio?: string; // Max 500 chars
email?: string;
dateOfBirth?: string;
}
// Response
{ success: true, data: { ...updatedUser } }GET /api/users/me/settings
Get user settings.
// Response
{
"success": true,
"data": {
"aliasMode": true,
"showOnlineStatus": false,
"regionMasking": true,
"photoBlurLevel": 100,
"disappearingMsgs": null
}
}PATCH /api/users/me/settings
Update settings.
Middleware: requireActiveStatus
// Request
{
aliasMode?: boolean;
showOnlineStatus?: boolean;
regionMasking?: boolean;
photoBlurLevel?: number; // 0-100
disappearingMsgs?: number; // Hours (0-168) or null
}DELETE /api/users/me
Soft delete account.
// Response
{ success: true, message: "Account deleted" }POST /api/users/me/panic-delete
Permanently delete all data.
// Response
{ success: true, message: "Account permanently deleted" }Discovery Routes
Base: /api/discover and /api/profilesAuth: Required + requireActiveStatus
GET /api/discover
Get discoverable profiles.
// Query params
{
page?: number; // Default: 1
limit?: number; // Default: 20, max: 100
gender?: 'MALE' | 'FEMALE';
verificationLevel?: 'NONE' | 'IDENTITY' | 'FULL';
}
// Response
{
"success": true,
"data": {
"items": [
{
"id": "uuid",
"alias": "mysterygirl",
"gender": "FEMALE",
"bio": "Looking for...",
"trustScore": 85,
"verificationLevel": "FULL",
"photos": [
{ "id": "uuid", "url": "https://...", "blurLevel": 100, "isPrimary": true }
]
}
],
"total": 150,
"page": 1,
"limit": 20,
"totalPages": 8
}
}GET /api/profiles/:id
Get single profile.
// Response
{
"success": true,
"data": {
"id": "uuid",
"alias": "mysterygirl",
"gender": "FEMALE",
"bio": "...",
"trustScore": 85,
"verificationLevel": "FULL",
"lastActiveAt": "2024-01-01T12:00:00Z", // null if hidden
"isMutualMatch": false,
"photos": [
{ "id": "uuid", "url": "...", "blurLevel": 80, "isPrimary": true }
// blurLevel forced to 80+ if not matched
]
}
}POST /api/profiles/:id/interest
Express interest in a profile.
// Response (no match)
{
"success": true,
"data": { "matched": false },
"message": "Interest expressed"
}
// Response (mutual match!)
{
"success": true,
"data": { "matched": true },
"message": "It's a match!"
}Match Routes
Base: /api/matchesAuth: Required + requireActiveStatus
GET /api/matches
Get all matches.
// Query: page, limit
// Response
{
"success": true,
"data": {
"items": [
{
"id": "interest-uuid",
"matchedAt": "2024-01-01T12:00:00Z",
"user": {
"id": "uuid",
"alias": "partner",
"bio": "...",
"trustScore": 90,
"verificationLevel": "FULL",
"photos": [{ ... }]
}
}
],
"total": 5,
"page": 1,
"limit": 20,
"totalPages": 1
}
}DELETE /api/matches/:id
Unmatch/decline match.
// Response
{ success: true, message: "Match removed" }Message Routes
Base: /apiAuth: Required + requireActiveStatus
GET /api/conversations
Get all conversations.
// Response
{
"success": true,
"data": {
"items": [
{
"id": "conv-uuid",
"otherUser": {
"id": "user-uuid",
"alias": "partner",
"photos": [{ "url": "...", "isPrimary": true }]
},
"lastMessage": {
"content": "Hey there!",
"createdAt": "2024-01-01T12:00:00Z",
"isFromMe": false
},
"unreadCount": 3,
"updatedAt": "2024-01-01T12:00:00Z"
}
],
"total": 10,
"page": 1,
"limit": 20,
"totalPages": 1
}
}GET /api/conversations/:id
Get messages in conversation.
// Response
{
"success": true,
"data": {
"items": [
{
"id": "msg-uuid",
"senderId": "user-uuid",
"recipientId": "other-uuid",
"content": "Hello!",
"mediaUrl": null,
"isRead": true,
"expiresAt": null,
"createdAt": "2024-01-01T12:00:00Z"
}
],
"total": 50,
"page": 1,
"limit": 20,
"totalPages": 3
}
}POST /api/conversations/:id
Send message.
// Request
{
content?: string; // Max 2000 chars
mediaUrl?: string; // URL
expiresIn?: number; // Hours (0-168)
}
// At least content OR mediaUrl required
// Response
{
"success": true,
"data": {
"id": "msg-uuid",
"senderId": "...",
"recipientId": "...",
"content": "Hello!",
"mediaUrl": null,
"isRead": false,
"expiresAt": null,
"createdAt": "..."
}
}DELETE /api/conversations/:id
Delete conversation (soft delete your messages).
// Response
{ success: true, message: "Conversation deleted" }POST /api/messages/:id/report
Report a message.
// Request
{
reason: 'BLACKMAIL' | 'HARASSMENT' | 'FAKE_PROFILE' | 'INAPPROPRIATE_CONTENT' | 'OTHER';
description: string; // Max 1000 chars
}
// Response
{ success: true, message: "Report submitted" }Wallet Routes
Base: /api/walletAuth: Required + requireActiveStatus
GET /api/wallet
Get wallet balance.
// Response
{
"success": true,
"data": {
"balance": 150.00,
"currency": "USD",
"userType": "earner" // or "spender"
}
}GET /api/wallet/transactions
Get transaction history.
// Response
{
"success": true,
"data": {
"items": [
{
"id": "tx-uuid",
"type": "EARNING",
"amount": 50.00,
"currency": "USD",
"status": "COMPLETED",
"reference": "match_bonus",
"createdAt": "2024-01-01T12:00:00Z"
}
],
"total": 20,
"page": 1,
"limit": 20,
"totalPages": 1
}
}POST /api/wallet/purchase
Purchase credits (male users only).
// Request
{
amount: number;
paymentReference: string;
}
// Response
{ success: true, message: "Credits purchased" }POST /api/wallet/withdraw
Request withdrawal (female users only).
// Request
{
amount: number; // Min $10
payoutDetails: string; // Payment method info
}
// Response
{ success: true, message: "Withdrawal requested" }GET /api/wallet/membership-tiers
Get available membership tiers.
// Response
{
"success": true,
"data": [
{
"id": "tier-uuid",
"name": "Standard",
"price": 49.00,
"currency": "USD",
"duration": 30,
"features": ["Worth verification", "Browse profiles", ...],
"isActive": true
}
]
}Verification Routes
Base: /api/verificationAuth: Required + requireActiveStatus
GET /api/verification/status
Get verification status.
// Response
{
"success": true,
"data": {
"currentLevel": "IDENTITY",
"verifications": [
{
"id": "ver-uuid",
"type": "IDENTITY",
"status": "APPROVED",
"documents": ["https://..."],
"notes": null,
"reviewedAt": "2024-01-01T12:00:00Z",
"createdAt": "..."
}
]
}
}POST /api/verification/identity
Submit identity verification.
// Request
{
documents: string[]; // 1-5 URLs
}
// Response
{ success: true, message: "Verification submitted" }POST /api/verification/worth
Submit worth verification (male only).
// Request
{ documents: string[] }
// Response
{ success: true, message: "Verification submitted" }POST /api/verification/value
Submit value verification (female only).
// Request
{ documents: string[] }
// Response
{ success: true, message: "Verification submitted" }Report Routes
Base: /api/reportsAuth: Required
POST /api/reports
Submit a report.
// Request
{
reportedId: string;
type: 'BLACKMAIL' | 'HARASSMENT' | 'FAKE_PROFILE' | 'INAPPROPRIATE_CONTENT' | 'OTHER';
description: string; // 10-1000 chars
evidence?: object;
}
// Response
{ success: true, data: { id: "report-uuid" } }POST /api/reports/blackmail
Submit urgent blackmail report.
// Request
{
reportedId: string;
description: string;
evidence?: object;
}
// Response (immediate -20 trust penalty applied)
{ success: true, data: { id: "report-uuid" } }GET /api/reports/my-reports
Get reports submitted by current user.
// Response
{
"success": true,
"data": [
{
"id": "report-uuid",
"type": "HARASSMENT",
"description": "...",
"status": "RESOLVED",
"reported": { "id": "...", "alias": "..." },
"createdAt": "..."
}
]
}Admin Routes
Base: /api/adminAuth: Required + requireMinRole('CURATOR')
GET /api/admin/dashboard
Dashboard statistics.
// Response
{
"success": true,
"data": {
"users": {
"total": 5000,
"male": 3000,
"female": 2000,
"active": 4500,
"pending": 100
},
"pendingVerifications": 15,
"pendingReports": 8,
"totalMatches": 1250
}
}GET /api/admin/users
List users with filters.
// Query: page, limit, status, gender, search
// Response
{
"success": true,
"data": {
"items": [...],
"total": 5000,
"page": 1,
"limit": 20,
"totalPages": 250
}
}GET /api/admin/users/:id
Get detailed user info.
PATCH /api/admin/users/:id
Update user (ADMIN+ only).
// Request
{
status?: 'PENDING' | 'ACTIVE' | 'SUSPENDED' | 'BANNED' | 'DELETED';
trustScore?: number; // 0-100
verificationLevel?: 'NONE' | 'IDENTITY' | 'FULL';
}POST /api/admin/users/:id/suspend
Suspend user (ADMIN+ only).
POST /api/admin/users/:id/ban
Ban user (ADMIN+ only).
POST /api/admin/users/:id/recalculate-trust
Recalculate trust score.
GET /api/admin/verifications
List pending verifications.
PATCH /api/admin/verifications/:id
Review verification.
// Request
{
approved: boolean;
notes?: string; // Max 500 chars
}GET /api/admin/reports
List reports.
// Query: page, limit, status, typeGET /api/admin/reports/:id
Get report details.
PATCH /api/admin/reports/:id
Handle report.
// Request
{
status: 'PENDING' | 'INVESTIGATING' | 'RESOLVED' | 'DISMISSED';
resolution?: string;
applyPenalty?: boolean;
penaltyAmount?: number; // 1-50
}GET /api/admin/transactions
List all transactions.
GET /api/admin/payouts
List withdrawal requests.
// Query: page, limit, statusPATCH /api/admin/payouts/:id
Process payout (ADMIN+ only).
// Request
{ approved: boolean }GET /api/admin/analytics
Get analytics data.
// Query: days (default 30)
// Response
{
"success": true,
"data": {
"userGrowth": [{ "date": "2024-01-01", "count": 50 }, ...],
"matchGrowth": [{ "date": "2024-01-01", "count": 25 }, ...],
"verificationStats": [{ "status": "APPROVED", "count": 100 }, ...],
"reportStats": [{ "type": "HARASSMENT", "count": 5 }, ...]
}
}Health Check
GET /health
// Response
{
"status": "ok",
"timestamp": "2024-01-01T12:00:00Z"
}Error Responses
All endpoints return consistent error format:
{
"success": false,
"error": "Error message description"
}HTTP Status Codes
| Code | Meaning |
|---|---|
| 400 | Bad Request (validation failed) |
| 401 | Unauthorized (not authenticated) |
| 403 | Forbidden (insufficient permissions) |
| 404 | Not Found |
| 409 | Conflict (duplicate resource) |
| 429 | Too Many Requests |
| 500 | Internal Server Error |