REST API
Authentication

Authentication & Account Management

Public Auth Routes

POST /auth/register

Register a free owner account, organisation, and store. The API returns a session immediately; signup does not send a phone OTP.

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",
    "tokens": { "access_token": "...", "refresh_token": "...", "expires_in": 900, "token_type": "Bearer" },
    "status": "active"
  }
}

Side Effects: Sends a welcome email and optional email verification link. No OTP or trial subscription is created.


POST /auth/otp/send

Send a 6-digit OTP via SMS.

Request Body:

{
  "phone": "+255712345678",
  "purpose": "login"
}

Purpose values: login, reset. Phone OTP is not part of signup.

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
  }
}

Onboarding metadata

Public business types, regions, districts, and pharmacy outlet types are documented in Onboarding metadata.

Admin authenticator MFA

Admin login can require a second step. When an enrolled admin signs in with a password, POST /auth/login returns mfa_required and an mfa_token instead of an access token. Submit that challenge and a six-digit authenticator code to POST /auth/mfa/totp/verify to complete login.

Admin authenticator management routes:

MethodPathPurpose
GET/accounts/mfa/totp/statusRead enrollment status.
POST/accounts/mfa/totp/setupBegin setup and receive the enrollment details.
POST/accounts/mfa/totp/verifyConfirm setup with a code.
POST/accounts/mfa/totp/disableDisable after password and authenticator verification.

These management endpoints require an authenticated admin session. See the API-owned MFA route implementation (opens in a new tab) for current access checks.