Authentication & Account Management
Public Auth Routes
POST /auth/register
Register a new owner account, organisation, store, and 7-day trial subscription.
Request Body:
{
"first_name": "John",
"last_name": "Doe",
"phone": "+255712345678",
"email": "john@example.com",
"password": "securepass",
"confirm_password": "securepass",
"occupation": "retailer",
"shop": {
"name": "My Shop",
"business_type": "retail",
"region": "Dar es Salaam",
"district": "Kinondoni",
"ward": "Msasani",
"street": "Sea View Rd",
"address": "P.O. Box 123",
"latitude": -6.7738,
"longitude": 39.2585
}
}Response (201):
{
"success": true,
"data": {
"user_id": "uuid",
"org_id": "uuid",
"store_id": "uuid",
"trial_ends_at": "2024-02-01T00:00:00Z",
"status": "pending_verification"
}
}Side Effects: Sends welcome email + email verification link. Creates 7-day trial subscription.
POST /auth/otp/send
Send a 6-digit OTP via SMS.
Request Body:
{
"phone": "+255712345678",
"purpose": "login"
}Purpose values: registration, login, reset
Response (200):
{
"success": true,
"message": "Code sent"
}POST /auth/otp/verify
Verify OTP and receive JWT tokens.
Request Body:
{
"phone": "+255712345678",
"purpose": "login",
"code": "123456"
}Response (200):
{
"success": true,
"data": {
"access_token": "eyJ...",
"refresh_token": "eyJ...",
"expires_in": 900,
"token_type": "Bearer"
}
}Side Effects: Marks phone as verified (if purpose is registration).
POST /auth/login
Password-based login with account lockout protection.
Request Body:
{
"identifier": "john@example.com",
"password": "securepass"
}Response (200):
{
"success": true,
"data": {
"access_token": "eyJ...",
"refresh_token": "eyJ...",
"expires_in": 900,
"token_type": "Bearer",
"user": {
"id": "uuid",
"first_name": "John",
"last_name": "Doe",
"email": "john@example.com",
"role": "owner"
}
}
}Lockout: After 5 failed attempts, the account is locked for 15 minutes.
POST /auth/refresh
Get a new token pair using a refresh token.
Request Body:
{
"refresh_token": "eyJ..."
}Response: Same as /auth/otp/verify.
GET /auth/confirm-email
Email verification link (opened from email).
Query: ?token=<verification_token>
Response: Redirects to {SITE_URL}/login?verified=1
POST /auth/password/forgot
Request a password reset OTP. Always returns success (no account enumeration).
Request Body:
{
"identifier": "john@example.com"
}Response (200):
{
"success": true,
"message": "If the account exists, a reset code has been sent"
}POST /auth/password/reset
Reset password using OTP.
Request Body:
{
"identifier": "john@example.com",
"otp": "123456",
"new_password": "newsecurepass",
"confirm_password": "newsecurepass"
}Response (200):
{
"success": true,
"message": "Password reset successfully"
}Authenticated Auth Routes
POST /auth/logout
Stateless logout (client discards tokens).
Auth: Required
Response (200):
{
"success": true,
"message": "Logged out"
}POST /auth/resend-confirmation
Resend email verification link.
Auth: Required
Response (200):
{
"success": true,
"message": "Confirmation email sent"
}Account Management
GET /accounts/me
Get current user's full profile including organisation and store data.
Auth: Required
Response (200):
{
"success": true,
"data": {
"user": { "id": "...", "first_name": "...", "role": "owner", ... },
"organisation": { "id": "...", "name": "...", ... },
"store": { "id": "...", "name": "...", ... },
"stores": [ ... ]
}
}PATCH /accounts/me
Update own profile.
Auth: Required
Request Body (all optional):
{
"first_name": "John",
"last_name": "Doe",
"email": "new@example.com",
"occupation": "retailer",
"password": "newpassword"
}GET /accounts/organisation
Get organisation details.
Auth: Required + Owner
PATCH /accounts/organisation
Update organisation settings and integration keys.
Auth: Required + Owner
Request Body (all optional):
{
"name": "My Organisation",
"legal_name": "My Org Ltd",
"tin": "123456789",
"business_type": "retail",
"region": "Dar es Salaam",
"district": "Kinondoni",
"ward": "Msasani",
"street": "Sea View Rd",
"address": "P.O. Box 123",
"latitude": -6.7738,
"longitude": 39.2585,
"phone": "+255712345678",
"email": "org@example.com",
"logo": "https://...",
"sendafrica_api_key": "SA-...",
"sms_sender_id": "ZiadaPOS",
"ngamia_api_key": "..."
}POST /accounts/switch-store
Switch active store context.
Auth: Required
Request Body:
{
"store_id": "uuid"
}Response: StoreResponse object.
GET /accounts/users
List all users in the organisation.
Auth: Required + Owner
Response: Array of UserResponse objects.
POST /accounts/users
Create a staff user. Temporary password sent via SMS.
Auth: Required + Owner
Request Body:
{
"first_name": "Jane",
"last_name": "Smith",
"phone": "+255712345679",
"email": "jane@example.com",
"role": "staff",
"store_id": "uuid",
"can_refund": false,
"can_discount": true,
"can_view_reports": false
}Response: UserResponse object.
DELETE /accounts/users/:id
Soft-deactivate a user.
Auth: Required + Owner
Response (204): No Content
GET /accounts/ai-credits
Get monthly AI credit usage.
Auth: Required
Response (200):
{
"success": true,
"data": {
"used": 45,
"allocated": 100,
"remaining": 55,
"month": "2024-01",
"percentage": 45.0
}
}Meta Data Endpoints
GET /meta/business-types
Returns list of available business types for registration.
Response: ["retail", "wholesale", "restaurant", "pharmacy", ...]
GET /meta/regions
Returns list of Tanzania regions.
Response: ["Arusha", "Dar es Salaam", "Dodoma", ...]
GET /meta/regions/:slug/districts
Returns districts for a given region.
Response: ["Kinondoni", "Ilala", "Temeke", ...]
GET /meta/occupations
Returns list of occupation options.
Response: ["retailer", "wholesaler", "manufacturer", ...]