REST API
Authentication

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", ...]