NextGate Authentication
Base URL: https://api.nextgate.com/api/v1
Short Description: NextGate Authentication API provides a comprehensive, passwordless-first authentication system with OAuth2 support (Google, Apple), multi-channel OTP verification, device tracking, session management, and a 6-step user onboarding flow. Designed for mobile-first applications with security features including risk assessment, rate limiting, and refresh token rotation.
Hints:
- ๐ Passwordless by Default: Users can authenticate via OTP (SMS/Email) without ever setting a password
- ๐ฑ Device Trust: Each device is tracked and can be managed by the user
- ๐ Token Rotation: Refresh tokens are rotated on each use for enhanced security
- โฑ๏ธ OTP Validity: All OTP codes expire in 10 minutes with max 3 verification attempts
- ๐ Multi-Channel: OTP can be sent via Email or SMS based on user preference
- ๐ Metadata System: Onboarding responses include
nextStepMetadatafor pre-filling forms
Authentication Architecture Overview
System Design Philosophy
NextGate Authentication is built on three core principles:
- Passwordless-First: Users authenticate primarily via OTP, with password as an optional secondary method
- Device-Aware Security: Every login tracks device information for risk assessment
- Progressive Onboarding: New users complete a 6-step guided setup process
Authentication Flow Diagram
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ NEXTGATE AUTHENTICATION FLOW โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโ
โ START โ
โ /auth/start โ
โโโโโโโโฌโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโ
โ โ โ
โผ โผ โผ
โโโโโโโโโโโโโ โโโโโโโโโโโโโ โโโโโโโโโโโโโ
โ PHONE โ โ EMAIL โ โ USERNAME โ
โ +255... โ โ @email.comโ โ @handle โ
โโโโโโโฌโโโโโโ โโโโโโโฌโโโโโโ โโโโโโโฌโโโโโโ
โ โ โ
โโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโ
โ โ
โผ โผ
โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ
โ NEW USER โ โ EXISTING USER โ
โ โ โ โ
โ โข Send OTP โ โ Has Password? โ
โ โข Create โ โโโโโโโโโฌโโโโโโโโ
โ Account โ โ
โโโโโโโโโฌโโโโโโโโ โโโโโโโโโโดโโโโโโโโโ
โ โ โ
โ โผ โผ
โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ
โ โ PASSWORD โ โ PASSWORDLESSโ
โ โ LOGIN โ โ (OTP SENT) โ
โ โโโโโโโโฌโโโโโโโ โโโโโโโโฌโโโโโโโ
โ โ โ
โ โโโโโโโโโโฌโโโโโโโโโ
โ โ
โผ โผ
โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ
โ VERIFY OTP โ โ DEVICE CHECK โ
โ /auth/verify โ โ โ
โโโโโโโโโฌโโโโโโโโ โ Known Device? โ
โ โโโโโโโโโฌโโโโโโโโ
โ โ
โ โโโโโโโโโโโดโโโโโโโโโโ
โ โ โ
โ โผ โผ
โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ
โ โ KNOWN โ โ NEW โ
โ โ DEVICE โ โ DEVICE โ
โ โ โ โ โ
โ โ โ Login OK โ โ โ Verify โ
โ โโโโโโโโโโโโโโโ โ via OTP โ
โ โโโโโโโโฌโโโโโโโ
โ โ
โผ โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ OTP VERIFIED โ
โโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโ
โ โ
โผ โผ
โโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโ
โ ONBOARDING โ โ FULLY โ
โ REQUIRED โ โ AUTHENTICATED โ
โ โ โ โ
โ (New User or โ โ โ Access Token โ
โ Incomplete) โ โ โ Refresh Token โ
โโโโโโโโโโโฌโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 6-STEP ONBOARDING โ
โ โ
โ โโโโโโโ โโโโโโโ โโโโโโโ โโโโโโโ โ
โ โ 1. โโโโถโ 2. โโโโถโ 3. โโโโถโ 4. โ โ
โ โ OTP โ โ AGE โ โUSER โ โINT- โ โ
โ โVRFY โ โ+NAMEโ โNAME โ โESTS โ โ
โ โโโโโโโ โโโโโโโ โโโโโโโ โโโโโโโ โ
โ โ โ
โ โผ โ
โ โโโโโโโ โ
โ โ 5. โ โ
โ โPRFL โ โ
โ โโโโฌโโโ โ
โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโ
โ COMPLETE โ
โ โ
โ โ Access Token โ
โ โ Refresh Token โ
โ โ Welcome! โ
โโโโโโโโโโโโโโโโโโโโโ
Token Types
| Token | Purpose | Expiry | Storage |
|---|---|---|---|
tempToken |
OTP verification, flow state | 10 min | Memory only |
onboardingToken |
Onboarding step progression | 30 min | Memory only |
onboardingRefreshToken |
Refresh onboarding token | 24 hours | Secure storage |
accessToken |
API authentication | 1 hour | Secure storage |
refreshToken |
Get new access tokens | 30 days | Secure storage |
deviceVerificationToken |
New device verification | 10 min | Memory only |
Onboarding Steps Explained
| Step | Enum Value | What Happens | Metadata Provided |
|---|---|---|---|
| 1 | INITIATED |
Account created, OTP sent | - |
| 2 | OTP_VERIFIED |
OTP confirmed, ready for age | firstName, lastName, profilePicture (from OAuth) |
| 3 | AGE_VERIFIED |
Age confirmed, names saved | suggestedUsernames (5 smart suggestions) |
| 4 | USERNAME_SET |
Username chosen | - |
| 5 | INTERESTS_SELECTED |
Interests saved | firstName, lastName, username, suggestedDisplayName, profilePicture |
| 6 | COMPLETED |
Profile complete, tokens issued | - |
Device Fingerprinting Guide
Why Device Fingerprinting?
Device fingerprinting creates a unique identifier for each device/browser combination. This enables:
- Security: Detect new/unknown devices attempting login
- Trust Levels: Known devices can skip additional verification
- Session Management: Users can see and revoke device access
Implementation for Frontend
Recommended Library: FingerprintJS
npm install @fingerprintjs/fingerprintjs
React Implementation
// hooks/useDeviceFingerprint.js
import { useState, useEffect } from 'react';
import FingerprintJS from '@fingerprintjs/fingerprintjs';
export const useDeviceFingerprint = () => {
const [fingerprint, setFingerprint] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const generateFingerprint = async () => {
try {
const fp = await FingerprintJS.load();
const result = await fp.get();
setFingerprint({
deviceId: result.visitorId,
confidence: result.confidence.score,
components: {
platform: result.components.platform?.value,
timezone: result.components.timezone?.value,
language: result.components.languages?.value?.[0],
screenResolution: result.components.screenResolution?.value,
colorDepth: result.components.colorDepth?.value,
}
});
} catch (error) {
console.error('Fingerprint generation failed:', error);
// Fallback: Generate a random UUID and store in localStorage
let fallbackId = localStorage.getItem('ng_device_id');
if (!fallbackId) {
fallbackId = crypto.randomUUID();
localStorage.setItem('ng_device_id', fallbackId);
}
setFingerprint({ deviceId: fallbackId, confidence: 0.5 });
} finally {
setLoading(false);
}
};
generateFingerprint();
}, []);
return { fingerprint, loading };
};
Usage in Authentication
// components/LoginForm.jsx
import { useDeviceFingerprint } from '../hooks/useDeviceFingerprint';
const LoginForm = () => {
const { fingerprint, loading } = useDeviceFingerprint();
const handleStartAuth = async (identifier) => {
if (loading || !fingerprint) {
console.warn('Device fingerprint not ready');
return;
}
const response = await fetch('/api/v1/auth/start', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
identifier: identifier,
deviceId: fingerprint.deviceId,
deviceName: getDeviceName(),
platform: getPlatform(),
}),
});
const data = await response.json();
// Handle response...
};
return (/* Your form JSX */);
};
// Helper: Generate human-readable device name
const getDeviceName = () => {
const ua = navigator.userAgent;
let browser = 'Unknown Browser';
let os = 'Unknown OS';
if (ua.includes('Chrome')) browser = 'Chrome';
else if (ua.includes('Firefox')) browser = 'Firefox';
else if (ua.includes('Safari')) browser = 'Safari';
else if (ua.includes('Edge')) browser = 'Edge';
if (ua.includes('Windows')) os = 'Windows';
else if (ua.includes('Mac')) os = 'macOS';
else if (ua.includes('Linux')) os = 'Linux';
else if (ua.includes('Android')) os = 'Android';
else if (ua.includes('iPhone') || ua.includes('iPad')) os = 'iOS';
return `${browser} on ${os}`;
};
// Helper: Get platform type
const getPlatform = () => {
const ua = navigator.userAgent;
if (ua.includes('Android')) return 'ANDROID';
if (ua.includes('iPhone') || ua.includes('iPad')) return 'IOS';
return 'WEB';
};
Alternative: Custom Fingerprint (No Library)
// utils/customFingerprint.js
export const generateCustomFingerprint = async () => {
const components = [];
// Screen properties
components.push(window.screen.width);
components.push(window.screen.height);
components.push(window.screen.colorDepth);
components.push(window.devicePixelRatio);
// Timezone & Language
components.push(Intl.DateTimeFormat().resolvedOptions().timeZone);
components.push(navigator.language);
components.push(navigator.platform);
components.push(navigator.hardwareConcurrency || 'unknown');
// Canvas fingerprint
try {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.textBaseline = 'top';
ctx.font = '14px Arial';
ctx.fillText('NextGate Device FP', 2, 2);
components.push(canvas.toDataURL());
} catch (e) {
components.push('canvas-blocked');
}
// WebGL renderer
try {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl');
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
if (debugInfo) {
components.push(gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL));
}
} catch (e) {
components.push('webgl-blocked');
}
// Create hash
const fingerprint = components.join('|||');
const encoder = new TextEncoder();
const data = encoder.encode(fingerprint);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
};
Required Headers for All Auth Requests
const authHeaders = {
'Content-Type': 'application/json',
'X-Device-Id': fingerprint.deviceId,
'X-Device-Name': getDeviceName(),
'X-Platform': getPlatform(),
};
// For authenticated requests:
authHeaders['Authorization'] = `Bearer ${accessToken}`;
// For session management:
authHeaders['X-Session-Id'] = sessionId;
Onboarding Flow
Visual Flow with API Calls
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ COMPLETE ONBOARDING FLOW โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
STEP 0: ENTRY POINT
โโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Two Entry Points โ
โ โ
โ A) Phone/Email Login B) OAuth (Google/Apple) โ
โ POST /auth/start POST /auth/login/oauth โ
โ โ โ โ
โ โผ โผ โ
โ OTP Sent to User Google Validates User โ
โ โ โ โ
โ โผ โ โ
โ POST /auth/verify โ โ
โ โ โ โ
โ โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ Response contains: โ
โ โข onboardingToken โ
โ โข currentStep โ
โ โข nextStep: AGE_VERIFIED โ
โ โข nextStepMetadata: { โ
โ firstName: "John", // from OAuth โ
โ lastName: "Doe", // from OAuth โ
โ profilePicture: "..." // from OAuth โ
โ hasOAuthData: true โ
โ } โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
STEP 1: AGE & NAME VERIFICATION (POST /auth/onboarding/age)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ FRONTEND SHOWS: โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ ๐
Let's verify your age โ โ
โ โ โ โ
โ โ First Name: [John____________] โ Pre-filled if OAuthโ โ
โ โ Last Name: [Doe_____________] โ Pre-filled if OAuthโ โ
โ โ Birth Date: [____/____/______] โ โ
โ โ โ โ
โ โ [Continue โ] โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ REQUEST: โ
โ { โ
โ "onboardingToken": "eyJ...", โ
โ "firstName": "John", โ
โ "lastName": "Doe", โ
โ "birthDate": "1999-05-15" โ
โ } โ
โ โ
โ RESPONSE: โ
โ { โ
โ "onboardingToken": "eyJ...", โ
โ "accountTier": "FULL", โ
โ "age": 25, โ
โ "currentStep": "AGE_VERIFIED", โ
โ "nextStep": "USERNAME_SET", โ
โ "nextStepMetadata": { โ
โ "suggestedUsernames": [ โ
โ "johndoe99", โ
โ "john_doe_official", โ
โ "jdoe_tz", โ
โ "the_johndoe", โ
โ "johnd_pro" โ
โ ] โ
โ } โ
โ } โ
โ โ
โ โ ๏ธ If age < 13: User blocked, account deleted โ
โ โ ๏ธ If age 13-17: Account tier = RESTRICTED โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
STEP 2: USERNAME SELECTION (POST /auth/onboarding/username)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ FRONTEND SHOWS: โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ ๐ค Choose your username โ โ
โ โ โ โ
โ โ @[________________] โ โ
โ โ โ โ
โ โ Suggestions: โ โ
โ โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โ โ
โ โ โ johndoe99 โ โ john_doe โ โ jdoe_tz โ โ โ
โ โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โ โ
โ โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โ โ
โ โ โ the_johndoe โ โ johnd_pro โ โ โ
โ โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โ โ
โ โ โ โ
โ โ [Continue โ] โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ REAL-TIME CHECK: POST /auth/onboarding/check-username โ
โ โ Returns availability + new suggestions if taken โ
โ โ
โ FINAL SUBMIT: โ
โ { โ
โ "onboardingToken": "eyJ...", โ
โ "username": "johndoe99" โ
โ } โ
โ โ
โ RESPONSE: โ
โ { โ
โ "onboardingToken": "eyJ...", โ
โ "username": "johndoe99", โ
โ "available": true, โ
โ "currentStep": "USERNAME_SET", โ
โ "nextStep": "INTERESTS_SELECTED" โ
โ } โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
STEP 3: INTERESTS SELECTION (POST /auth/onboarding/interests)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ FRONTEND SHOWS: โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ ๐ฏ What are you interested in? โ โ
โ โ Select at least 3 โ โ
โ โ โ โ
โ โ โโโโโโโโโโ โโโโโโโโโโ โโโโโโโโโโ โโโโโโโโโโ โ โ
โ โ โ๐ตMusic โ โโฝSportsโ โ๐ฎGamingโ โ๐ฑTech โ โ โ
โ โ โโโโโโโโโโ โโโโโโโโโโ โโโโโโโโโโ โโโโโโโโโโ โ โ
โ โ โโโโโโโโโโ โโโโโโโโโโ โโโโโโโโโโ โโโโโโโโโโ โ โ
โ โ โ๐ฌMoviesโ โ๐Books โ โ๐Food โ โโ๏ธTravelโ โ โ
โ โ โโโโโโโโโโ โโโโโโโโโโ โโโโโโโโโโ โโโโโโโโโโ โ โ
โ โ ... more โ โ
โ โ โ โ
โ โ [Continue โ] โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ FETCH CATEGORIES: GET /interests/categories/all โ
โ โ
โ SUBMIT: โ
โ { โ
โ "onboardingToken": "eyJ...", โ
โ "interestIds": ["uuid-music", "uuid-tech", "uuid-travel"]โ
โ } โ
โ โ
โ RESPONSE: โ
โ { โ
โ "onboardingToken": "eyJ...", โ
โ "selectedInterests": ["Music", "Tech", "Travel"], โ
โ "count": 3, โ
โ "currentStep": "INTERESTS_SELECTED", โ
โ "nextStep": "PROFILE_COMPLETED", โ
โ "nextStepMetadata": { โ
โ "firstName": "John", โ
โ "lastName": "Doe", โ
โ "username": "johndoe99", โ
โ "suggestedDisplayName": "John Doe", โ
โ "profilePicture": "https://..." โ
โ } โ
โ } โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
STEP 4: PROFILE COMPLETION (POST /auth/onboarding/profile)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ FRONTEND SHOWS: โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โจ Complete your profile โ โ
โ โ โ โ
โ โ โโโโโโโโโโโ โ โ
โ โ โ ๐ท โ โ Pre-filled from OAuth โ โ
โ โ โ [image] โ or upload new โ โ
โ โ โโโโโโโโโโโ โ โ
โ โ โ โ
โ โ Display Name: [John Doe________] โ Pre-filled โ โ
โ โ @johndoe99 (cannot change here) โ โ
โ โ โ โ
โ โ Bio: [_________________________________] โ โ
โ โ [_________________________________] โ โ
โ โ โ โ
โ โ [๐ Complete Setup] โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ REQUEST: โ
โ { โ
โ "onboardingToken": "eyJ...", โ
โ "displayName": "John Doe", โ
โ "bio": "Tech enthusiast | Music lover", โ
โ "profilePictureUrl": "https://..." โ
โ } โ
โ โ
โ HEADERS (for device registration): โ
โ X-Device-Id: "abc123fingerprint" โ
โ X-Device-Name: "Chrome on macOS" โ
โ X-Platform: "WEB" โ
โ โ
โ RESPONSE: โ
โ { โ
โ "accessToken": "eyJ...", โ
โ "refreshToken": "eyJ...", โ
โ "systemUsername": "su_uuid", โ
โ "username": "johndoe99", โ
โ "displayName": "John Doe", โ
โ "currentStep": "COMPLETED", โ
โ "onboardingComplete": true, โ
โ "message": "Welcome to NextGate!" โ
โ } โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๐ WELCOME! โ
โ โ
โ User is now fully โ
โ authenticated and โ
โ can access the app โ
โโโโโโโโโโโโโโโโโโโโโโโโโ
Handling Onboarding Resume
When a user returns with incomplete onboarding:
const handleOnboardingResponse = (response) => {
const { currentStep, nextStep, nextStepMetadata, onboardingToken } = response.data;
sessionStorage.setItem('onboardingToken', onboardingToken);
switch (nextStep) {
case 'AGE_VERIFIED':
navigate('/onboarding/age', { state: { metadata: nextStepMetadata } });
break;
case 'USERNAME_SET':
navigate('/onboarding/username', { state: { metadata: nextStepMetadata } });
break;
case 'INTERESTS_SELECTED':
navigate('/onboarding/interests');
break;
case 'PROFILE_COMPLETED':
navigate('/onboarding/profile', { state: { metadata: nextStepMetadata } });
break;
case 'COMPLETED':
handleFullAuth(response.data);
break;
}
};
Standard Response Format
All API responses follow a consistent structure:
Success Response Structure:
{
"success": true,
"httpStatus": "OK",
"message": "Operation completed successfully",
"action_time": "2025-01-24T10:30:45",
"data": { }
}
Error Response Structure:
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Error description",
"action_time": "2025-01-24T10:30:45",
"data": "Error description"
}
Standard Response Fields:
| Field | Type | Description |
|---|---|---|
success |
boolean | true for successful operations, false for errors |
httpStatus |
string | HTTP status name (OK, BAD_REQUEST, NOT_FOUND, etc.) |
message |
string | Human-readable message |
action_time |
string | ISO 8601 timestamp |
data |
object/string | Response payload or error details |
API Endpoints
Authentication Initialization
1. Start Authentication
Purpose: Initiate authentication flow for phone, email, or username
Endpoint: POST {base_url}/auth/start
Access Level: ๐ Public
Authentication: None
Request JSON Sample:
{
"identifier": "+255712345678",
"deviceId": "a1b2c3d4e5f6789",
"deviceName": "Chrome on macOS",
"platform": "WEB"
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| identifier | string | Yes | Phone (+255...), email, or @username | Valid format |
| deviceId | string | Yes | Device fingerprint | Non-empty |
| deviceName | string | Yes | Human-readable device name | Non-empty |
| platform | string | Yes | Device platform | enum: IOS, ANDROID, WEB |
Success Response JSON Sample (New User):
{
"success": true,
"httpStatus": "OK",
"message": "Authentication initiated",
"action_time": "2025-01-24T10:30:45",
"data": {
"tempToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"maskedIdentifier": "โขโขโข โขโขโข โขโข78",
"provider": "PHONE",
"otpExpiresIn": 600,
"isNewUser": true,
"onboardingComplete": false,
"currentStep": "INITIATED",
"hasPassword": false,
"requiresOtpChannelSelection": false,
"message": "OTP sent to โขโขโข โขโขโข โขโข78"
}
}
Success Response JSON Sample (Existing User with Password):
{
"success": true,
"httpStatus": "OK",
"message": "Authentication initiated",
"action_time": "2025-01-24T10:30:45",
"data": {
"tempToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"maskedIdentifier": "jโขโขโขโขโขโข@gโขโขโขโข.com",
"provider": "EMAIL",
"otpExpiresIn": 600,
"isNewUser": false,
"onboardingComplete": true,
"currentStep": "COMPLETED",
"hasPassword": true,
"requiresOtpChannelSelection": false,
"message": "Choose login method: password or OTP"
}
}
Success Response JSON Sample (Username - Channel Selection Required):
{
"success": true,
"httpStatus": "OK",
"message": "Authentication initiated",
"action_time": "2025-01-24T10:30:45",
"data": {
"tempToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"hasPassword": true,
"requiresOtpChannelSelection": true,
"availableChannels": [
{ "type": "EMAIL", "maskedValue": "jโขโขโขโขโขโข@gโขโขโขโข.com", "isPrimary": true },
{ "type": "PHONE", "maskedValue": "โขโขโข โขโขโข โขโข78", "isPrimary": false }
],
"isNewUser": false,
"onboardingComplete": true,
"message": "Select where to receive OTP"
}
}
Success Response Fields:
| Field | Description |
|---|---|
| tempToken | Temporary token for next step |
| maskedIdentifier | Masked phone/email for display |
| provider | Identifier type: PHONE, EMAIL |
| otpExpiresIn | OTP validity in seconds (600 = 10 minutes) |
| isNewUser | Whether this is a new registration |
| onboardingComplete | Whether user completed onboarding |
| currentStep | Current onboarding step |
| hasPassword | Whether user has set a password |
| requiresOtpChannelSelection | If true, user must choose OTP channel |
| availableChannels | List of available OTP channels |
Error Responses:
Identifier Blocked (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "This phone is blocked",
"action_time": "2025-01-24T10:30:45",
"data": "This phone is blocked"
}
Account Not Found - Username (404):
{
"success": false,
"httpStatus": "NOT_FOUND",
"message": "Account not found",
"action_time": "2025-01-24T10:30:45",
"data": "Account not found"
}
2. Verify Authentication OTP
Purpose: Verify OTP and complete authentication or continue to onboarding
Endpoint: POST {base_url}/auth/verify
Access Level: ๐ Public
Authentication: None
Request JSON Sample:
{
"tempToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"otp": "123456",
"deviceId": "a1b2c3d4e5f6789",
"deviceName": "Chrome on macOS",
"platform": "WEB"
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| tempToken | string | Yes | Token from /auth/start | Valid JWT |
| otp | string | Yes | 6-digit OTP code | Exactly 6 digits |
| deviceId | string | No | Device fingerprint | - |
| deviceName | string | No | Human-readable device name | - |
| platform | string | No | Device platform | enum: IOS, ANDROID, WEB |
Success Response JSON Sample (New User - Start Onboarding):
{
"success": true,
"httpStatus": "OK",
"message": "Verification successful",
"action_time": "2025-01-24T10:30:45",
"data": {
"onboardingToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"onboardingComplete": false,
"currentStep": "OTP_VERIFIED",
"nextStep": "AGE_VERIFIED",
"nextStepMetadata": null,
"message": "OTP verified. Let's set up your account."
}
}
Success Response JSON Sample (OAuth User - Has Profile Data):
{
"success": true,
"httpStatus": "OK",
"message": "Verification successful",
"action_time": "2025-01-24T10:30:45",
"data": {
"onboardingToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"onboardingComplete": false,
"currentStep": "OTP_VERIFIED",
"nextStep": "AGE_VERIFIED",
"nextStepMetadata": {
"firstName": "John",
"lastName": "Doe",
"profilePicture": "https://lh3.googleusercontent.com/...",
"hasOAuthData": true
},
"message": "OTP verified. Let's set up your account."
}
}
Success Response JSON Sample (Existing User - Fully Authenticated):
{
"success": true,
"httpStatus": "OK",
"message": "Verification successful",
"action_time": "2025-01-24T10:30:45",
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"systemUsername": "su_550e8400-e29b-41d4-a716-446655440000",
"username": "johndoe99",
"displayName": "John Doe",
"accountTier": "FULL",
"onboardingComplete": true,
"currentStep": "COMPLETED",
"message": "Login successful"
}
}
Error Responses:
Invalid OTP (403):
{
"success": false,
"httpStatus": "FORBIDDEN",
"message": "Invalid OTP code",
"action_time": "2025-01-24T10:30:45",
"data": "Invalid OTP code"
}
Max Attempts Exceeded (403):
{
"success": false,
"httpStatus": "FORBIDDEN",
"message": "Maximum verification attempts exceeded",
"action_time": "2025-01-24T10:30:45",
"data": "Maximum verification attempts exceeded"
}
3. Send OTP to Channel
Purpose: Send OTP to a specific channel when user has multiple options
Endpoint: POST {base_url}/auth/send-otp-to-channel
Access Level: ๐ Public
Authentication: None
Request JSON Sample:
{
"tempToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"channel": "EMAIL"
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| tempToken | string | Yes | Token from /auth/start | Valid JWT |
| channel | string | Yes | Preferred OTP channel | enum: EMAIL, PHONE |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "OTP sent",
"action_time": "2025-01-24T10:30:45",
"data": {
"tempToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"sentTo": "EMAIL",
"maskedDestination": "jโขโขโขโขโขโข@gโขโขโขโข.com",
"expiresIn": 600,
"message": "OTP sent to email"
}
}
4. Login with Password
Purpose: Authenticate using password instead of OTP
Endpoint: POST {base_url}/auth/login/password
Access Level: ๐ Public
Authentication: None
Request JSON Sample:
{
"tempToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"password": "mySecurePassword123",
"deviceId": "a1b2c3d4e5f6789",
"deviceName": "Chrome on macOS",
"platform": "WEB"
}
Success Response JSON Sample (Known Device):
{
"success": true,
"httpStatus": "OK",
"message": "Login successful",
"action_time": "2025-01-24T10:30:45",
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"systemUsername": "su_550e8400-e29b-41d4-a716-446655440000",
"username": "johndoe99",
"displayName": "John Doe",
"accountTier": "FULL",
"newDevice": false,
"requiresDeviceVerification": false,
"message": "Login successful"
}
}
Success Response JSON Sample (New Device - Verification Required):
{
"success": true,
"httpStatus": "OK",
"message": "Login successful",
"action_time": "2025-01-24T10:30:45",
"data": {
"newDevice": true,
"requiresDeviceVerification": true,
"deviceVerificationToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"requiresChannelSelection": false,
"otpSentTo": "โขโขโข โขโขโข โขโข78",
"message": "Device verification required. OTP sent."
}
}
Error Responses:
Invalid Password (403):
{
"success": false,
"httpStatus": "FORBIDDEN",
"message": "Invalid password",
"action_time": "2025-01-24T10:30:45",
"data": "Invalid password"
}
5. OAuth Login (Google)
Purpose: Authenticate using Google OAuth2 authorization code flow
Endpoint: POST {base_url}/auth/login/oauth
Access Level: ๐ Public
Authentication: None
Request JSON Sample:
{
"provider": "GOOGLE",
"code": "4/0AX4XfWh...",
"redirectUri": "https://app.nextgate.com/auth/callback",
"state": "random-state-string",
"deviceId": "a1b2c3d4e5f6789",
"deviceName": "Chrome on macOS",
"platform": "WEB"
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| provider | string | Yes | OAuth provider | enum: GOOGLE, APPLE |
| code | string | Yes | Authorization code | Non-empty |
| redirectUri | string | Yes | Redirect URI | Must match OAuth config |
| state | string | No | State for CSRF protection | - |
| deviceId | string | No | Device fingerprint | - |
| deviceName | string | No | Device name | - |
| platform | string | No | Device platform | enum: IOS, ANDROID, WEB |
Success Response JSON Sample (New User):
{
"success": true,
"httpStatus": "OK",
"message": "OAuth login processed",
"action_time": "2025-01-24T10:30:45",
"data": {
"newUser": true,
"onboardingToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"currentStep": "OTP_VERIFIED",
"nextStep": "AGE_VERIFIED",
"nextStepMetadata": {
"firstName": "John",
"lastName": "Doe",
"profilePicture": "https://lh3.googleusercontent.com/a/...",
"hasOAuthData": true
},
"message": "Complete your registration",
"state": "random-state-string"
}
}
Success Response JSON Sample (Existing User):
{
"success": true,
"httpStatus": "OK",
"message": "OAuth login processed",
"action_time": "2025-01-24T10:30:45",
"data": {
"newUser": false,
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"systemUsername": "su_550e8400-e29b-41d4-a716-446655440000",
"username": "johndoe99",
"displayName": "John Doe",
"accountTier": "FULL",
"message": "Login successful",
"state": "random-state-string"
}
}
6. Verify Device
Purpose: Verify a new device using OTP
Endpoint: POST {base_url}/auth/device/verify
Access Level: ๐ Public
Authentication: None
Request JSON Sample:
{
"deviceVerificationToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"otp": "123456",
"deviceId": "a1b2c3d4e5f6789",
"deviceName": "Chrome on macOS",
"platform": "WEB"
}
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Device verified",
"action_time": "2025-01-24T10:30:45",
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"systemUsername": "su_550e8400-e29b-41d4-a716-446655440000",
"username": "johndoe99",
"displayName": "John Doe",
"accountTier": "FULL",
"message": "Login successful"
}
}
7. Resend OTP
Purpose: Resend OTP code (with rate limiting)
Endpoint: POST {base_url}/auth/resend-otp
Access Level: ๐ Public
Authentication: None
Request JSON Sample:
{
"tempToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "OTP resend processed",
"action_time": "2025-01-24T10:30:45",
"data": {
"tempToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"maskedDestination": "โขโขโข โขโขโข โขโข78",
"sentTo": "PHONE",
"expiresIn": 600,
"attemptsRemaining": 4,
"cooldownSeconds": 0,
"message": "OTP resent"
}
}
Cooldown Response:
{
"success": true,
"httpStatus": "OK",
"message": "OTP resend processed",
"action_time": "2025-01-24T10:30:45",
"data": {
"cooldownSeconds": 45,
"attemptsRemaining": 3,
"message": "Please wait 45 seconds before requesting again"
}
}
Onboarding Endpoints
8. Set Age (Step 1)
Purpose: Verify user's age and save first/last name
Endpoint: POST {base_url}/auth/onboarding/age
Access Level: ๐ Public (requires valid onboarding token)
Authentication: None (onboarding token in body)
Request JSON Sample:
{
"onboardingToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"firstName": "John",
"lastName": "Doe",
"birthDate": "1999-05-15"
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| onboardingToken | string | Yes | Token from previous step | Valid JWT |
| firstName | string | Yes | User's first name | 1-50 characters |
| lastName | string | Yes | User's last name | 1-50 characters |
| birthDate | string | Yes | Date of birth | ISO date (YYYY-MM-DD), must be in past |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Age verified",
"action_time": "2025-01-24T10:30:45",
"data": {
"onboardingToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"accountTier": "FULL",
"age": 25,
"restricted": false,
"blocked": false,
"currentStep": "AGE_VERIFIED",
"nextStep": "USERNAME_SET",
"nextStepMetadata": {
"suggestedUsernames": [
"johndoe99",
"john_doe_official",
"jdoe_tz",
"the_johndoe",
"johnd_pro"
]
},
"message": "Age verified successfully"
}
}
Blocked Response (Under 13):
{
"success": true,
"httpStatus": "OK",
"message": "Age verified",
"action_time": "2025-01-24T10:30:45",
"data": {
"accountTier": null,
"age": 12,
"restricted": true,
"blocked": true,
"currentStep": "AGE_VERIFIED",
"nextStep": null,
"message": "You must be at least 13 years old to use NextGate"
}
}
9. Check Username Availability
Purpose: Check if a username is available (for real-time validation)
Endpoint: POST {base_url}/auth/onboarding/check-username
Access Level: ๐ Public
Authentication: None
Request JSON Sample:
{
"username": "johndoe99"
}
Success Response JSON Sample (Available):
{
"success": true,
"httpStatus": "OK",
"message": "Username availability checked",
"action_time": "2025-01-24T10:30:45",
"data": {
"username": "johndoe99",
"available": true
}
}
Success Response JSON Sample (Not Available):
{
"success": true,
"httpStatus": "OK",
"message": "Username not available",
"action_time": "2025-01-24T10:30:45",
"data": {
"username": "johndoe",
"available": false,
"suggestions": ["johndoe99", "johndoe_official", "the_johndoe"]
}
}
10. Set Username (Step 2)
Purpose: Set the user's chosen username
Endpoint: POST {base_url}/auth/onboarding/username
Access Level: ๐ Public (requires valid onboarding token)
Authentication: None (onboarding token in body)
Request JSON Sample:
{
"onboardingToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"username": "johndoe99"
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| onboardingToken | string | Yes | Token from previous step | Valid JWT |
| username | string | Yes | Chosen username | 3-30 chars, starts with letter, alphanumeric + underscore |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Username set",
"action_time": "2025-01-24T10:30:45",
"data": {
"onboardingToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"username": "johndoe99",
"available": true,
"currentStep": "USERNAME_SET",
"nextStep": "INTERESTS_SELECTED",
"message": "Username set successfully"
}
}
11. Get Interest Categories
Purpose: Get all available interest categories for selection
Endpoint: GET {base_url}/interests/categories/all
Access Level: ๐ Public
Authentication: None
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Categories retrieved",
"action_time": "2025-01-24T10:30:45",
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440001",
"name": "Music",
"icon": "๐ต",
"description": "Concerts, artists, playlists",
"displayOrder": 1,
"isActive": true
},
{
"id": "550e8400-e29b-41d4-a716-446655440002",
"name": "Sports",
"icon": "โฝ",
"description": "Games, teams, fitness",
"displayOrder": 2,
"isActive": true
}
]
}
12. Set Interests (Step 3)
Purpose: Save user's selected interests
Endpoint: POST {base_url}/auth/onboarding/interests
Access Level: ๐ Public (requires valid onboarding token)
Authentication: None (onboarding token in body)
Request JSON Sample:
{
"onboardingToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"interestIds": [
"550e8400-e29b-41d4-a716-446655440001",
"550e8400-e29b-41d4-a716-446655440002",
"550e8400-e29b-41d4-a716-446655440003"
]
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| onboardingToken | string | Yes | Token from previous step | Valid JWT |
| interestIds | array | Yes | List of category UUIDs | Min 3 items |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Interests saved",
"action_time": "2025-01-24T10:30:45",
"data": {
"onboardingToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"selectedInterests": ["Music", "Sports", "Technology"],
"count": 3,
"currentStep": "INTERESTS_SELECTED",
"nextStep": "PROFILE_COMPLETED",
"nextStepMetadata": {
"firstName": "John",
"lastName": "Doe",
"username": "johndoe99",
"suggestedDisplayName": "John Doe",
"profilePicture": "https://lh3.googleusercontent.com/a/..."
},
"message": "Interests saved successfully"
}
}
13. Set Profile (Step 4 - Final)
Purpose: Complete profile setup and finish onboarding
Endpoint: POST {base_url}/auth/onboarding/profile
Access Level: ๐ Public (requires valid onboarding token)
Authentication: None (onboarding token in body)
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| X-Device-Id | string | No | Device fingerprint |
| X-Device-Name | string | No | Device name |
| X-Platform | string | No | Platform (IOS, ANDROID, WEB) |
Request JSON Sample:
{
"onboardingToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"displayName": "John Doe",
"bio": "Tech enthusiast | Music lover | Based in Dar es Salaam ๐น๐ฟ",
"profilePictureUrl": "https://storage.nextgate.com/profiles/abc123.jpg"
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| onboardingToken | string | Yes | Token from previous step | Valid JWT |
| displayName | string | Yes | Public display name | 1-50 characters |
| bio | string | No | User biography | Max 160 characters |
| profilePictureUrl | string | No | Profile picture URL | Valid URL |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Welcome to NextGate!",
"action_time": "2025-01-24T10:30:45",
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"systemUsername": "su_550e8400-e29b-41d4-a716-446655440000",
"username": "johndoe99",
"displayName": "John Doe",
"currentStep": "COMPLETED",
"onboardingComplete": true,
"message": "Welcome to NextGate!"
}
}
Token Management
14. Refresh Access Token
Purpose: Get a new access token using refresh token (with rotation)
Endpoint: POST {base_url}/auth/token/refresh
Access Level: ๐ Public
Authentication: None (refresh token in body)
Request JSON Sample:
{
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Token refreshed",
"action_time": "2025-01-24T10:30:45",
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"tokenType": "Bearer",
"expiresIn": 3600,
"message": "Tokens refreshed successfully"
}
}
โ ๏ธ Important: The refresh token is rotated on each use. Always store the new
refreshTokenfrom the response.
Error Response (Token Reuse Detected):
{
"success": false,
"httpStatus": "UNAUTHORIZED",
"message": "Security alert: Token reuse detected. Please login again.",
"action_time": "2025-01-24T10:30:45",
"data": "Security alert: Token reuse detected. Please login again."
}
15. Revoke Token
Purpose: Revoke a refresh token (logout)
Endpoint: POST {base_url}/auth/token/revoke
Access Level: ๐ Public
Authentication: None
Request JSON Sample:
{
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Token revoked successfully",
"action_time": "2025-01-24T10:30:45",
"data": null
}
16. Refresh Onboarding Token
Purpose: Refresh onboarding token if it's about to expire
Endpoint: POST {base_url}/auth/token/refresh-onboarding
Access Level: ๐ Public
Authentication: None
Request JSON Sample:
{
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Onboarding token refreshed",
"action_time": "2025-01-24T10:30:45",
"data": {
"onboardingToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"currentStep": "USERNAME_SET",
"nextStep": "INTERESTS_SELECTED",
"expiresIn": 1800,
"message": "Token refreshed successfully"
}
}
Password Management
17. Initiate Forgot Password
Purpose: Start password reset flow
Endpoint: POST {base_url}/auth/password/forgot/initiate
Access Level: ๐ Public
Authentication: None
Request JSON Sample:
{
"identifier": "johndoe99"
}
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Request processed",
"action_time": "2025-01-24T10:30:45",
"data": {
"tempToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"maskedDestination": "jโขโขโขโขโขโข@gโขโขโขโข.com",
"sentTo": "EMAIL",
"expiresIn": 600,
"otpVerified": false,
"passwordReset": false,
"requiresChannelSelection": false,
"message": "OTP sent to email"
}
}
18. Verify Forgot Password OTP
Purpose: Verify OTP for password reset
Endpoint: POST {base_url}/auth/password/forgot/verify-otp
Access Level: ๐ Public
Authentication: None
Request JSON Sample:
{
"tempToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"otp": "123456"
}
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "OTP verified",
"action_time": "2025-01-24T10:30:45",
"data": {
"resetToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"otpVerified": true,
"passwordReset": false,
"message": "OTP verified. Set your new password."
}
}
19. Reset Forgotten Password
Purpose: Set new password after OTP verification
Endpoint: POST {base_url}/auth/password/forgot/reset
Access Level: ๐ Public
Authentication: None
Request JSON Sample:
{
"resetToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"newPassword": "newSecurePassword123",
"confirmPassword": "newSecurePassword123"
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| resetToken | string | Yes | Token from verify-otp | Valid JWT |
| newPassword | string | Yes | New password | Min 8 characters |
| confirmPassword | string | Yes | Confirmation | Must match newPassword |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Password reset successfully",
"action_time": "2025-01-24T10:30:45",
"data": {
"otpVerified": true,
"passwordReset": true,
"message": "Password reset successfully"
}
}
โ ๏ธ Note: After password reset, all active sessions are revoked. User must login again.
20. Change Password (Authenticated)
Purpose: Change password for logged-in user
Endpoint: POST {base_url}/password/change
Access Level: ๐ Protected
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer {accessToken} |
Request JSON Sample:
{
"currentPassword": "oldPassword123",
"newPassword": "newSecurePassword456",
"confirmPassword": "newSecurePassword456"
}
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Password changed",
"action_time": "2025-01-24T10:30:45",
"data": {
"success": true,
"hadPassword": true,
"message": "Password changed successfully"
}
}
21. Set Password (for Passwordless Users)
Purpose: Set password for users who registered via OAuth or OTP only
Endpoint: POST {base_url}/password/set
Access Level: ๐ Protected
Authentication: Bearer Token
Request JSON Sample:
{
"newPassword": "myNewPassword123",
"confirmPassword": "myNewPassword123"
}
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Password set successfully",
"action_time": "2025-01-24T10:30:45",
"data": {
"success": true,
"hadPassword": false,
"message": "Password set successfully. You can now login with password."
}
}
Session Management
22. Get All Sessions
Purpose: List all active sessions for the user
Endpoint: GET {base_url}/auth/sessions
Access Level: ๐ Protected
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer {accessToken} |
| X-Session-Id | string | No | Current session ID |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Sessions retrieved",
"action_time": "2025-01-24T10:30:45",
"data": {
"sessions": [
{
"id": "550e8400-e29b-41d4-a716-446655440001",
"deviceId": "a1b2c3d4e5f6789",
"deviceName": "Chrome on macOS",
"platform": "WEB",
"ipAddress": "192.168.1.1",
"location": "Dar es Salaam, Tanzania",
"lastActiveAt": "2025-01-24T10:30:45",
"createdAt": "2025-01-20T08:00:00",
"currentSession": true
},
{
"id": "550e8400-e29b-41d4-a716-446655440002",
"deviceId": "xyz789abc123",
"deviceName": "Safari on iPhone",
"platform": "IOS",
"ipAddress": "192.168.1.2",
"location": "Arusha, Tanzania",
"lastActiveAt": "2025-01-23T15:20:00",
"createdAt": "2025-01-15T12:00:00",
"currentSession": false
}
],
"totalCount": 2
}
}
23. Sign Out (Current Session)
Purpose: End the current session
Endpoint: POST {base_url}/auth/sessions/sign-out
Access Level: ๐ Protected
Authentication: Bearer Token
Request JSON Sample:
{
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Signed out successfully",
"action_time": "2025-01-24T10:30:45",
"data": null
}
24. Sign Out All Sessions
Purpose: End all sessions including current (security logout)
Endpoint: POST {base_url}/auth/sessions/sign-out-all
Access Level: ๐ Protected
Authentication: Bearer Token
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "All sessions terminated",
"action_time": "2025-01-24T10:30:45",
"data": {
"terminatedCount": 4,
"message": "All 4 sessions have been terminated. Please login again."
}
}
25. Revoke Specific Session
Purpose: End a specific session by ID
Endpoint: DELETE {base_url}/auth/sessions/{sessionId}
Access Level: ๐ Protected
Authentication: Bearer Token
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| sessionId | string | Yes | Session UUID to revoke |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Session revoked",
"action_time": "2025-01-24T10:30:45",
"data": {
"sessionId": "550e8400-e29b-41d4-a716-446655440002",
"message": "Session has been terminated"
}
}
Device Management
26. Get All Devices
Purpose: List all registered/trusted devices
Endpoint: GET {base_url}/auth/devices
Access Level: ๐ Protected
Authentication: Bearer Token
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer {accessToken} |
| X-Device-Id | string | No | Current device ID |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Devices retrieved",
"action_time": "2025-01-24T10:30:45",
"data": {
"devices": [
{
"id": "550e8400-e29b-41d4-a716-446655440001",
"deviceId": "a1b2c3d4e5f6789",
"deviceName": "Chrome on macOS",
"platform": "WEB",
"lastIpAddress": "192.168.1.1",
"lastLocation": "Dar es Salaam, Tanzania",
"lastActiveAt": "2025-01-24T10:30:45",
"firstSeenAt": "2025-01-01T08:00:00",
"trustLevel": "TRUSTED",
"isCurrentDevice": true
}
],
"totalCount": 1
}
}
27. Remove Device
Purpose: Remove a device from trusted devices (revokes all sessions on that device)
Endpoint: DELETE {base_url}/auth/devices/{deviceId}
Access Level: ๐ Protected
Authentication: Bearer Token
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| deviceId | string | Yes | Device record UUID |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Device removed",
"action_time": "2025-01-24T10:30:45",
"data": {
"deviceId": "550e8400-e29b-41d4-a716-446655440002",
"sessionsRevoked": 2,
"message": "Device removed and 2 sessions terminated"
}
}
Account Linking
28. Get Linked Accounts
Purpose: List all linked OAuth providers and identifiers
Endpoint: GET {base_url}/auth/linked-accounts
Access Level: ๐ Protected
Authentication: Bearer Token
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Linked accounts retrieved",
"action_time": "2025-01-24T10:30:45",
"data": {
"linkedAccounts": [
{
"type": "EMAIL",
"value": "john.doe@gmail.com",
"isPrimary": true,
"isVerified": true,
"linkedAt": "2025-01-01T08:00:00"
},
{
"type": "PHONE",
"value": "+255712345678",
"isPrimary": false,
"isVerified": true,
"linkedAt": "2025-01-05T10:00:00"
},
{
"type": "OAUTH",
"provider": "GOOGLE",
"email": "john.doe@gmail.com",
"linkedAt": "2025-01-01T08:00:00"
}
],
"hasPassword": true,
"canRemoveEmail": true,
"canRemovePhone": true
}
}
29. Link Email
Purpose: Add a new email to the account
Endpoint: POST {base_url}/auth/linked-accounts/email/link
Access Level: ๐ Protected
Authentication: Bearer Token
Request JSON Sample:
{
"email": "john.work@company.com"
}
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Verification email sent",
"action_time": "2025-01-24T10:30:45",
"data": {
"tempToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"email": "jโขโขโขโขโขโข@cโขโขโขโขโขโข.com",
"expiresIn": 600,
"message": "Verification code sent"
}
}
30. Link Phone
Purpose: Add a new phone number to the account
Endpoint: POST {base_url}/auth/linked-accounts/phone/link
Access Level: ๐ Protected
Authentication: Bearer Token
Request JSON Sample:
{
"phone": "+255787654321"
}
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Verification SMS sent",
"action_time": "2025-01-24T10:30:45",
"data": {
"tempToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"phone": "โขโขโข โขโขโข โขโข21",
"expiresIn": 600,
"message": "Verification code sent"
}
}
31. Link OAuth Provider
Purpose: Link a new OAuth provider to existing account
Endpoint: POST {base_url}/auth/linked-accounts/oauth/link
Access Level: ๐ Protected
Authentication: Bearer Token
Request JSON Sample:
{
"provider": "APPLE",
"code": "authorization_code_from_apple",
"redirectUri": "https://app.nextgate.com/auth/callback"
}
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "OAuth provider linked",
"action_time": "2025-01-24T10:30:45",
"data": {
"provider": "APPLE",
"email": "john.doe@icloud.com",
"linkedAt": "2025-01-24T10:30:45",
"message": "Apple account linked successfully"
}
}
32. Unlink OAuth Provider
Purpose: Remove an OAuth provider from the account
Endpoint: DELETE {base_url}/auth/linked-accounts/oauth/{provider}
Access Level: ๐ Protected
Authentication: Bearer Token
Path Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| provider | string | Yes | GOOGLE or APPLE |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "OAuth provider unlinked",
"action_time": "2025-01-24T10:30:45",
"data": {
"provider": "APPLE",
"message": "Apple account unlinked"
}
}
Error Response (Last Login Method):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Cannot unlink: This is your only login method.",
"action_time": "2025-01-24T10:30:45",
"data": "Cannot unlink: This is your only login method."
}
Error Reference
Standard Error Codes
| HTTP Code | Status | Common Causes |
|---|---|---|
| 400 | BAD_REQUEST | Invalid request data, item already exists |
| 401 | UNAUTHORIZED | Missing/invalid/expired token |
| 403 | FORBIDDEN | Invalid OTP, max attempts exceeded, blocked |
| 404 | NOT_FOUND | Resource doesn't exist |
| 422 | UNPROCESSABLE_ENTITY | Validation errors |
| 429 | TOO_MANY_REQUESTS | Rate limit exceeded |
| 500 | INTERNAL_SERVER_ERROR | Server error |
Authentication-Specific Errors
| Error Message | HTTP Code | Resolution |
|---|---|---|
| "Token has expired" | 401 | Refresh token or re-authenticate |
| "Invalid OTP code" | 403 | Re-enter correct code |
| "Maximum verification attempts exceeded" | 403 | Request new OTP |
| "This phone is blocked" | 400 | Contact support |
| "Account not found" | 404 | Check identifier |
| "Security alert: Token reuse detected" | 401 | Login again |
| "Login blocked: Too many failed attempts" | 400 | Wait or reset password |
| "You must be at least 13 years old" | N/A | Cannot use service |
Frontend Integration Checklist
Before Starting
- Install FingerprintJS:
npm install @fingerprintjs/fingerprintjs - Set up secure token storage (httpOnly cookies or secure storage)
- Configure API base URL
Authentication Flow
- Implement device fingerprint generation on app load
- Handle all response types from
/auth/start - Implement OTP input with 6-digit validation
- Handle device verification flow
- Store tokens securely after successful auth
Onboarding Flow
- Pre-fill forms using
nextStepMetadatawhen available - Display username suggestions as clickable chips
- Implement real-time username availability check (debounced)
- Load interest categories from API
- Require minimum 3 interests
- Handle profile picture upload
Token Management
- Implement automatic token refresh before expiry
- Handle 401 responses globally (redirect to login)
- Store new refresh token after each rotation
- Clear all tokens on logout
Security Best Practices
- Never store tokens in localStorage - Use httpOnly cookies or secure native storage
- Always send device fingerprint - Required for device trust tracking
- Handle token rotation - Always save new refresh token after refresh
- Validate OTP client-side - Only allow 6 digits before API call
- Rate limit on frontend - Disable resend button during cooldown
- Clear tokens on security events - Token reuse detection, password reset
End of Documentation