Skip to content

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:3000

All endpoints prefixed with /api/


Quick Reference

GroupEndpointsAuth RequiredDescription
Auth9PartialRegistration, login, OTP
Users6Profile & settings
Discover3Browse profiles
Matches2View & manage matches
Messages5Conversations & chat
Wallet5Balance & transactions
Verification4Submit & check status
Reports3Submit reports
Admin15✓ (CURATOR+)Admin dashboard
Total52

Authentication Routes

Base: /api/auth

POST /api/auth/apply

Create account application and send OTP.

Rate Limit: authLimiter (10 requests/15 min)

Request:

typescript
// 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:

json
{
  "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)

typescript
// Request
{ phoneNumber: string }

// Response
{ success: true, message: "Verification code sent", data: { expiresAt: string } }

POST /api/auth/confirm-phone

Verify OTP code.

Rate Limit: authLimiter

typescript
// 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

typescript
// 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

typescript
// Request
{
  phoneNumber: string;
  password: string;
}

// Response
{
  "success": true,
  "data": {
    "accessToken": string,
    "refreshToken": string
  }
}

POST /api/auth/refresh

Rotate access token.

typescript
// Request
{ refreshToken: string }

// Response
{ success: true, data: { accessToken: string, refreshToken: string } }

POST /api/auth/logout

Revoke tokens.

Auth: Required

typescript
// 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

typescript
// Request
{ phoneNumber: string }

// Response
{ success: true, message: "Reset code sent" }

POST /api/auth/reset-password

Reset password with OTP.

Rate Limit: authLimiter

typescript
// 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.

typescript
// 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

typescript
// 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.

typescript
// Response
{
  "success": true,
  "data": {
    "aliasMode": true,
    "showOnlineStatus": false,
    "regionMasking": true,
    "photoBlurLevel": 100,
    "disappearingMsgs": null
  }
}

PATCH /api/users/me/settings

Update settings.

Middleware: requireActiveStatus

typescript
// 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.

typescript
// Response
{ success: true, message: "Account deleted" }

POST /api/users/me/panic-delete

Permanently delete all data.

typescript
// Response
{ success: true, message: "Account permanently deleted" }

Discovery Routes

Base: /api/discover and /api/profilesAuth: Required + requireActiveStatus

GET /api/discover

Get discoverable profiles.

typescript
// 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.

typescript
// 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.

typescript
// 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.

typescript
// 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.

typescript
// Response
{ success: true, message: "Match removed" }

Message Routes

Base: /apiAuth: Required + requireActiveStatus

GET /api/conversations

Get all conversations.

typescript
// 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.

typescript
// 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.

typescript
// 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).

typescript
// Response
{ success: true, message: "Conversation deleted" }

POST /api/messages/:id/report

Report a message.

typescript
// 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.

typescript
// Response
{
  "success": true,
  "data": {
    "balance": 150.00,
    "currency": "USD",
    "userType": "earner"  // or "spender"
  }
}

GET /api/wallet/transactions

Get transaction history.

typescript
// 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).

typescript
// Request
{
  amount: number;
  paymentReference: string;
}

// Response
{ success: true, message: "Credits purchased" }

POST /api/wallet/withdraw

Request withdrawal (female users only).

typescript
// 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.

typescript
// 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.

typescript
// 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.

typescript
// Request
{
  documents: string[];  // 1-5 URLs
}

// Response
{ success: true, message: "Verification submitted" }

POST /api/verification/worth

Submit worth verification (male only).

typescript
// Request
{ documents: string[] }

// Response
{ success: true, message: "Verification submitted" }

POST /api/verification/value

Submit value verification (female only).

typescript
// Request
{ documents: string[] }

// Response
{ success: true, message: "Verification submitted" }

Report Routes

Base: /api/reportsAuth: Required

POST /api/reports

Submit a report.

typescript
// 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.

typescript
// 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.

typescript
// 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.

typescript
// 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.

typescript
// 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).

typescript
// 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.

typescript
// Request
{
  approved: boolean;
  notes?: string;  // Max 500 chars
}

GET /api/admin/reports

List reports.

typescript
// Query: page, limit, status, type

GET /api/admin/reports/:id

Get report details.


PATCH /api/admin/reports/:id

Handle report.

typescript
// 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.

typescript
// Query: page, limit, status

PATCH /api/admin/payouts/:id

Process payout (ADMIN+ only).

typescript
// Request
{ approved: boolean }

GET /api/admin/analytics

Get analytics data.

typescript
// 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

typescript
// Response
{
  "status": "ok",
  "timestamp": "2024-01-01T12:00:00Z"
}

Error Responses

All endpoints return consistent error format:

typescript
{
  "success": false,
  "error": "Error message description"
}

HTTP Status Codes

CodeMeaning
400Bad Request (validation failed)
401Unauthorized (not authenticated)
403Forbidden (insufficient permissions)
404Not Found
409Conflict (duplicate resource)
429Too Many Requests
500Internal Server Error

One chat. Everything done.