# Frontend API Guide

Internal engineering document. Do not publish this guide to platform users. Public users should see the policy pages in `docs/legal`, not implementation endpoints or developer workflows.

This guide is for web and mobile engineers building the Smart Estate UI. It explains what each API area does, which user roles can use it, what screens it supports, and how to handle common responses.

For raw request examples, also see:

- `docs/api.http`
- `docs/api-endpoints.md`
- `docs/production-readiness-checklist.md`
- `docs/legal/public-policy-links.md`
- Swagger: `https://api.gatewise.ng/docs` after super-admin console login

Before frontend signoff, run the WebApp and MobileApp acceptance checks in `docs/production-readiness-checklist.md`. This is required because both clients depend on the same auth, estate scope, payment, OTP, and guard-device rules.

Public legal, privacy, data protection, data deletion, and cookies/cache links must use the WebApp Policy Center as the canonical source. The MobileApp should link to `https://gatewise.ng/legal` and the related policy URLs instead of carrying separate policy text.

Current sibling frontend workspaces:

- WebApp: `..\Company Documents\Estate Security Platform Strategy\FE\WebApp`
- MobileApp: `..\Company Documents\Estate Security Platform Strategy\FE\MobileApp\gatewise_ng`

## Base URLs

Local:

```text
API_BASE_URL=http://localhost:4000/api/v1
```

Production:

```text
API_BASE_URL=https://api.gatewise.ng/v1
WEBAPP_ORIGIN=https://gatewise.ng
MOBILEAPP_API_BASE_URL=https://api.gatewise.ng/v1
```

All endpoint paths below are relative to `API_BASE_URL`. The WebApp at `https://gatewise.ng` and the MobileApp should both use `https://api.gatewise.ng/v1`.

## Product Roles

| Role | Main UI |
| --- | --- |
| `SUPER_ADMIN` | Platform dashboard, estate onboarding review, payment provider settings, OTP provider settings, third-party API keys, subscription plans |
| `SALES_ADMIN` | Sales/operations dashboard, multi-estate visibility, estate creation/update, lead pipeline where enabled; payment records/actions only when super admin enables them |
| `ESTATE_ADMIN` | Estate dashboard, residents, guards, visitors, passes, incidents |
| `SECURITY_SUPERVISOR` | Guards, residents, incidents, offline operations |
| `GUARD` | Gate scanner/mobile app, check-in, checkout, incidents, offline sync |
| `RESIDENT` | Resident app, visitors, visitor passes |

## Authentication Model

The API uses phone OTP login.

1. Call `POST /auth/login` with a phone number.
2. User receives OTP by the active OTP provider.
3. Call `POST /auth/verify-otp`.
4. Store `accessToken` and `refreshToken`.
5. Call `GET /features/me` and cache effective feature keys for menus/actions.
6. Send `Authorization: Bearer <accessToken>` on protected requests.
7. On access-token expiry, call `POST /auth/refresh`.

Access tokens are short-lived. The frontend should automatically refresh once on `401 Invalid bearer token`, then retry the original request. If refresh fails, log the user out.

## Standard Headers

```http
Content-Type: application/json
Accept: application/json
Authorization: Bearer <accessToken>
```

`Authorization` is required for protected endpoints only.

## Standard Error Shape

Most errors follow:

```json
{
  "message": "Human readable message",
  "error": "Unauthorized",
  "statusCode": 401
}
```

Validation errors may return `message` as an array of field errors.

Common statuses:

| Status | Meaning | UI behavior |
| --- | --- | --- |
| `400` | Invalid request or business rule failed | Show inline or toast error |
| `401` | Missing/invalid token, invalid OTP, inactive account | Refresh token or return to login |
| `403` | Role or estate scope blocked | Show permission/access message |
| `404` | Record not found | Show empty state or not-found state |
| `409` | Duplicate phone, device, or reference | Show conflict message on the form |
| `429` | OTP/auth throttled | Disable retry briefly |
| `502/503` | External gateway/provider failure | Show retry/support message |

## Frontend Route Guards

Recommended web route groups:

```text
/login                    Public
/super-admin/*            SUPER_ADMIN
/sales-admin/*            SALES_ADMIN
/estate-admin/*           ESTATE_ADMIN
/supervisor/*             SECURITY_SUPERVISOR
/guard/*                  GUARD
/resident/*               RESIDENT
```

Decode the access token payload to get:

```json
{
  "sub": "user-id",
  "estateId": "estate-id-or-null",
  "role": "SUPER_ADMIN",
  "exp": 1778879565
}
```

Use decoded role only for route grouping. Use `/features/me` for feature-level navigation, buttons, tabs, and mobile screen availability. The API remains the authority.

MobileApp exception: `SALES_ADMIN` and partner workspaces are WebApp-only. After OTP verification, MobileApp should route `SALES_ADMIN` to an access-blocked screen and should not render partner, sales pipeline, commission, or platform-admin screens.

## Feature Access

The backend enforces feature settings. Frontend apps should mirror the same settings to avoid showing blocked actions.

Required startup flow after login:
1. Decode role from token.
2. Call `GET /features/me`.
3. Build a `Set<string>` from rows where `effectiveEnabled` is `true`.
4. Hide disabled routes/actions and show a permission message on `403 Feature ... is not enabled`.

Key endpoints:
- `GET /features/catalog` - Stable feature keys, labels, categories, defaults.
- `GET /features/me` - Current user effective feature access.
- `GET /features/role-settings?role=SALES_ADMIN` - Admin feature settings view.
- `PATCH /features/role-settings/:role` - Super admin updates any role; estate admin updates supported resident/guard estate features.

Important UI rules:
- Sales admin screens must be feature-driven. Do not expose payment records, payment confirmation, provider settings, OTP settings, or integration keys unless `/features/me` returns those feature keys enabled.
- Sales partner earnings/contact screens belong in WebApp only. Use `SALES_PARTNER_PROFILE_VIEW`, `SALES_PARTNER_EARNINGS_VIEW`, and `SALES_PARTNER_ADMIN_CONTACT` to control tabs, cards, and buttons.
- Estate admin feature settings UI should show only supported `RESIDENT` and `GUARD` estate features.
- Estate admin feature toggles must respect `planEnabled`. If `planEnabled` is false, show the feature as unavailable under the current subscription plan and do not send an enable request.
- WebApp and MobileApp should both use the same feature keys from `/features/catalog`; avoid duplicating a separate hardcoded permission matrix.

## Auth Endpoints

### POST `/auth/login`

Starts login by sending an OTP to a registered phone number.

UI screens:
- Login screen
- Super-admin console OTP request
- Mobile login

Request:

```json
{
  "phone": "+2348000000001"
}
```

Success:

```json
{
  "message": "OTP sent",
  "provider": "TERMII",
  "providerPriority": ["TERMII", "AFRICAS_TALKING"],
  "expiresInSeconds": 300
}
```

In local `DEV` mode, response may include:

```json
{
  "devOtp": "123456"
}
```

Common errors:
- `401 No account exists for this phone number`
- `401 Account is not active`
- `429 Too Many Requests`

### POST `/auth/verify-otp`

Verifies OTP and returns tokens.

Request:

```json
{
  "phone": "+2348000000001",
  "otp": "123456"
}
```

Success:

```json
{
  "accessToken": "jwt",
  "refreshToken": "jwt"
}
```

Common errors:
- `401 Invalid OTP`
- `401 Estate is not active`

### POST `/auth/refresh`

Rotates refresh token and returns fresh tokens.

Request:

```json
{
  "refreshToken": "jwt"
}
```

Success:

```json
{
  "accessToken": "jwt",
  "refreshToken": "jwt"
}
```

Common errors:
- `401 Invalid refresh token`
- `401 Refresh token is invalid or revoked`

### POST `/auth/signup`

Protected admin account creation. Use this when admins create users inside the app. It is not public self-signup.

Roles:
- `SUPER_ADMIN`
- `ESTATE_ADMIN`
- `SECURITY_SUPERVISOR`

Request for resident:

```json
{
  "estateId": "estate-id",
  "name": "New Resident",
  "phone": "+2348000000100",
  "role": "RESIDENT",
  "unit": "B14"
}
```

Request for guard:

```json
{
  "estateId": "estate-id",
  "name": "Gate Guard",
  "phone": "+2348000000101",
  "role": "GUARD",
  "deviceId": "gate-tablet-001"
}
```

Common errors:
- `400 unit is required when signing up a resident`
- `403 You cannot create users with role ...`
- `409 A user with this phone number already exists`

## Public Estate Onboarding

These endpoints support the landing page or sales flow for potential estate admins.

### GET `/onboarding/subscription-plans`

Lists public active plans.

UI screens:
- Pricing page
- Estate signup plan selector

Success item:

```json
{
  "code": "STARTER",
  "name": "Starter",
  "minResidents": 0,
  "maxResidents": 100,
  "monthlyPriceKobo": 25000000,
  "currency": "NGN",
  "customPricing": false,
  "description": "0-100 residents/units",
  "isActive": true
}
```

### GET `/onboarding/payment-providers?estateId=...`

Lists payment providers and whether each is enabled for the estate.

UI screens:
- Plan/payment provider selector

Success item:

```json
{
  "provider": "FLUTTERWAVE",
  "displayName": "Flutterwave",
  "globallyEnabled": true,
  "estateEnabled": null,
  "effectiveEnabled": true,
  "sandboxMode": "sandbox",
  "sandboxConfigured": true,
  "recommendedUseCases": ["Nigeria payments"]
}
```

### POST `/onboarding/estate-admin-signup`

Public lead generation for estate admins. Creates estate in `PENDING`, creates estate admin as `SUSPENDED`, then waits for super-admin review.

UI screens:
- Public estate signup form
- Sales lead form

Request:

```json
{
  "estateName": "GreenPark Estate",
  "location": "Lagos, Nigeria",
  "unitCount": 120,
  "estateType": "Residential gated community",
  "adminName": "Estate Admin",
  "adminPhone": "+2348000000990",
  "adminEmail": "estate.admin@example.com",
  "metadata": {
    "source": "website"
  }
}
```

Success:

```json
{
  "estate": {
    "id": "estate-id",
    "status": "PENDING",
    "signupStatus": "PENDING_APPROVAL"
  },
  "admin": {
    "id": "user-id",
    "name": "Estate Admin",
    "phone": "+2348000000990",
    "role": "ESTATE_ADMIN",
    "status": "SUSPENDED"
  },
  "nextStep": "Super admin review is required before plan selection and activation."
}
```

Common errors:
- `409 A user with this phone number already exists`

### POST `/onboarding/estates/:estateId/select-plan`

Estate admin selects plan and provider after super-admin approval.

Request:

```json
{
  "adminPhone": "+2348000000990",
  "planCode": "GROWTH",
  "paymentProvider": "FLUTTERWAVE"
}
```

Common errors:
- `400 Estate signup must be approved before selecting a plan`
- `400 FLUTTERWAVE is not enabled for this estate`

### POST `/onboarding/estates/:estateId/payments/initialize`

Creates a hosted checkout payment with the selected provider.

Request:

```json
{
  "adminPhone": "+2348000000990",
  "customerEmail": "estate.admin@example.com",
  "customerName": "Estate Admin",
  "planCode": "GROWTH",
  "paymentProvider": "FLUTTERWAVE",
  "metadata": {
    "channel": "card"
  }
}
```

Success:

```json
{
  "payment": {
    "id": "payment-id",
    "status": "PENDING",
    "reference": "FLU-..."
  },
  "checkout": {
    "provider": "FLUTTERWAVE",
    "mode": "sandbox",
    "checkoutUrl": "https://checkout.example"
  },
  "nextStep": "Open checkoutUrl, complete sandbox payment, then ask a super admin to confirm the payment."
}
```

Frontend behavior:
- Redirect or open `checkout.checkoutUrl`.
- After returning from provider, show pending confirmation state.

### GET `/onboarding/payments/callback`

Payment provider redirect endpoint. Usually not called manually by frontend.

Frontend behavior:
- Show a "payment received, awaiting activation" screen if user returns here in browser.

### POST `/onboarding/estates/:estateId/payments`

Records manual/offline payment reference.

Request:

```json
{
  "adminPhone": "+2348000000990",
  "reference": "BANK-TRANSFER-001",
  "metadata": {
    "channel": "manual"
  }
}
```

## Estate Management

### GET `/estates`

Lists estates.

Roles:
- `SUPER_ADMIN`: all estates
- `ESTATE_ADMIN`: own estate only

UI screens:
- Super-admin estate list
- Estate admin settings header

### POST `/estates`

Creates an active estate directly. This is a super-admin backend operation, not the public lead flow.

Role:
- `SUPER_ADMIN`

Request:

```json
{
  "name": "GreenPark Estate",
  "location": "Lagos, Nigeria",
  "unitCount": 120,
  "estateType": "Residential gated community",
  "settings": {
    "timezone": "Africa/Lagos"
  },
  "branding": {
    "primaryColor": "#0f766e"
  }
}
```

## Residents

### GET `/residents?estateId=...&q=...`

Lists/searches residents for an estate.

Roles:
- `ESTATE_ADMIN`
- `SECURITY_SUPERVISOR`
- `RESIDENT`

UI screens:
- Resident directory
- Visitor pass host selector
- Admin resident management

Query:
- `estateId`: required for super admin, optional for estate-scoped users
- `q`: optional search by name, unit, or phone

### POST `/residents`

Creates resident user account and resident profile.

Roles:
- `ESTATE_ADMIN`
- `SECURITY_SUPERVISOR`

Request:

```json
{
  "estateId": "estate-id",
  "name": "Ada Okafor",
  "phone": "+2348000000300",
  "unit": "A12"
}
```

Common errors:
- `409 A resident with this phone number already exists`
- `403 Cross-estate access is not allowed`

## Guards

### GET `/guards?estateId=...`

Lists guards for an estate.

Roles:
- `ESTATE_ADMIN`
- `SECURITY_SUPERVISOR`

UI screens:
- Guard directory
- Gate device assignment
- Incident/check-in reporting selectors

### POST `/guards`

Creates guard user account and guard profile. Guards do not self-register.

Roles:
- `ESTATE_ADMIN`
- `SECURITY_SUPERVISOR`

Request:

```json
{
  "estateId": "estate-id",
  "name": "Main Gate Guard",
  "phone": "+2348000000200",
  "deviceId": "gate-tablet-001"
}
```

Device rules:
- `deviceId` is optional.
- First check-in with a `deviceId` binds the device if no device is already assigned.
- A different device later returns `403 Guard account is assigned to a different device`.

## Visitors

### GET `/visitors?estateId=...&q=...`

Lists/searches visitors.

Roles:
- `ESTATE_ADMIN`
- `SECURITY_SUPERVISOR`
- `GUARD`
- `RESIDENT`

UI screens:
- Visitor directory
- Resident app visitor history
- Admin visitor search

### POST `/visitors`

Creates a visitor record.

Roles:
- `ESTATE_ADMIN`
- `RESIDENT`

Request:

```json
{
  "estateId": "estate-id",
  "name": "John Doe",
  "phone": "+2348111111111",
  "vehiclePlate": "LND-123-AB",
  "photoUrl": "https://example.com/photo.jpg"
}
```

## Visitor Passes

### GET `/visitors/passes?estateId=...`

Lists visitor passes.

Roles:
- `ESTATE_ADMIN`
- `SECURITY_SUPERVISOR`
- `GUARD`
- `RESIDENT`

UI screens:
- Resident pass list
- Gate expected visitor list
- Admin pass management

### POST `/visitors/passes`

Creates a QR/manual-code visitor pass.

Roles:
- `ESTATE_ADMIN`
- `RESIDENT`

Request:

```json
{
  "estateId": "estate-id",
  "visitorId": "visitor-id",
  "hostResidentId": "resident-id",
  "passType": "SINGLE_USE",
  "validFrom": "2026-05-16T08:00:00.000Z",
  "validUntil": "2026-05-16T18:00:00.000Z",
  "maxEntries": 1
}
```

Success includes:

```json
{
  "id": "pass-id",
  "visitorCode": "ABC12345",
  "qrToken": "token",
  "qrPayload": {
    "token": "token"
  }
}
```

Frontend behavior:
- Render `qrPayload.token` as QR code.
- Show `visitorCode` as manual fallback.
- Show validity window and pass status.

### POST `/visitors/passes/:id/revoke`

Revokes active pass.

Roles:
- `ESTATE_ADMIN`
- `SECURITY_SUPERVISOR`

## Gate Check-In

These are guard app endpoints.

### POST `/checkin/scan`

Checks in visitor using QR token.

Role:
- `GUARD`

Request:

```json
{
  "estateId": "estate-id",
  "qrToken": "qr-token",
  "guardId": "guard-id",
  "deviceId": "gate-tablet-001",
  "entryGate": "Main Gate",
  "offlineEventId": "optional-client-event-id"
}
```

Success:

```json
{
  "id": "visit-session-id",
  "status": "CHECKED_IN",
  "checkinTime": "2026-05-16T10:00:00.000Z",
  "visitorPass": {
    "visitor": {
      "name": "John Doe"
    },
    "hostResident": {
      "unit": "A12"
    }
  }
}
```

Common errors:
- `404 Visitor pass not found`
- `400 Visitor pass is outside its validity window`
- `400 Visitor pass has no remaining entries`
- `403 Guard account is assigned to a different device`

### POST `/checkin/manual`

Checks in visitor using manual visitor code.

Role:
- `GUARD`

Request:

```json
{
  "estateId": "estate-id",
  "visitorCode": "ABC12345",
  "guardId": "guard-id",
  "deviceId": "gate-tablet-001",
  "entryGate": "Main Gate"
}
```

### POST `/checkin/checkout`

Checks out an open visit session.

Role:
- `GUARD`

Request:

```json
{
  "visitSessionId": "visit-session-id",
  "guardId": "guard-id",
  "deviceId": "gate-tablet-001",
  "exitGate": "Main Gate"
}
```

## Incidents

### GET `/incidents?estateId=...`

Lists incident reports.

Roles:
- `ESTATE_ADMIN`
- `SECURITY_SUPERVISOR`
- `GUARD`

### POST `/incidents`

Creates incident report.

Roles:
- `ESTATE_ADMIN`
- `SECURITY_SUPERVISOR`
- `GUARD`

Request:

```json
{
  "estateId": "estate-id",
  "guardId": "guard-id",
  "type": "SECURITY",
  "description": "Suspicious activity near the main gate",
  "metadata": {
    "gate": "Main Gate"
  }
}
```

## Offline Guard Mode

### GET `/offline/bootstrap?estateId=...`

Downloads active passes, residents, and recent audit logs for offline gate use.

Roles:
- `GUARD`
- `ESTATE_ADMIN`
- `SECURITY_SUPERVISOR`

Frontend behavior:
- Cache response locally.
- Use `serverTime` to compare validity windows.
- Refresh when app regains network.

### POST `/offline/sync`

Uploads offline event records. Current implementation stores and marks them as `APPLIED`; deeper domain replay is a future phase.

Roles:
- `GUARD`
- `ESTATE_ADMIN`
- `SECURITY_SUPERVISOR`

Request:

```json
{
  "estateId": "estate-id",
  "deviceId": "guard-device-001",
  "items": [
    {
      "clientEventId": "offline-event-001",
      "eventType": "VISITOR_CHECKED_IN",
      "payload": {
        "visitorCode": "ABC12345"
      }
    }
  ]
}
```

## Notifications

### POST `/notifications/send`

Queues notification request in audit log. This is not yet a full SMS/push delivery service.

Roles:
- `ESTATE_ADMIN`
- `SECURITY_SUPERVISOR`

Request:

```json
{
  "estateId": "estate-id",
  "channel": "SMS",
  "recipient": "+2348111111111",
  "message": "Your visitor pass is ready",
  "metadata": {
    "visitorPassId": "visitor-pass-id"
  }
}
```

Success:

```json
{
  "status": "QUEUED",
  "channel": "SMS",
  "criticalPath": false
}
```

## Super Admin And Sales Admin Endpoints

Most endpoints below require `SUPER_ADMIN`. `SALES_ADMIN` can access only the routes whose feature keys are enabled for that role. By default sales admins can view platform/estate operational information and create/update estates, but payment records, payment confirmation, payment-provider settings, OTP settings, and integration keys remain hidden unless `SUPER_ADMIN` enables those features.

For sales admins without `PAYMENTS_VIEW`, dashboard payment totals, estate-signup payment arrays, and payment-related audit activity are omitted by the API.

### GET `/admin/dashboard`

Returns platform counts and recent audit logs.

UI screens:
- Super-admin overview dashboard

Success:

```json
{
  "totals": {
    "estates": 1,
    "pendingEstateSignups": 0,
    "pendingPayments": 0,
    "users": 4,
    "residents": 1,
    "guards": 1,
    "visitors": 10,
    "activePasses": 3,
    "openVisits": 1,
    "incidents": 2
  },
  "recentAuditLogs": []
}
```

### GET `/admin/users?estateId=&role=&status=`

Lists users with optional filters.

### PATCH `/admin/users/:id`

Updates user account and syncs resident/guard profile fields when applicable.

Request:

```json
{
  "name": "Updated User",
  "phone": "+2348000000999",
  "status": "SUSPENDED",
  "estateId": "estate-id"
}
```

### GET `/admin/estates`

Lists all estates.

### PATCH `/admin/estates/:id`

Updates estate details, settings, or branding.

Request:

```json
{
  "name": "GreenPark Estate",
  "location": "Lagos, Nigeria",
  "unitCount": 120,
  "estateType": "Residential gated community",
  "settings": {
    "timezone": "Africa/Lagos"
  },
  "branding": {
    "primaryColor": "#0f766e"
  }
}
```

### GET `/admin/estate-signups?status=PENDING_APPROVAL`

Lists estate signup leads.

### PATCH `/admin/estate-signups/:estateId/review`

Approves, rejects, or suspends signup.

Request:

```json
{
  "status": "APPROVED",
  "reviewNotes": "Approved for subscription plan selection."
}
```

### PATCH `/admin/estate-signups/:estateId/confirm-payment`

Confirms pending payment, verifies hosted gateway payments, activates estate, and activates estate admin account.

Request:

```json
{
  "paymentReference": "FLU-1778881112222-a1b2c3d4"
}
```

### GET `/admin/subscription-plans?includeInactive=true`

Lists subscription plans, including the `features` array that controls which estate-level features can be enabled under each plan.

UI screens:
- Super-admin plan settings
- Public pricing comparison through `/onboarding/subscription-plans`
- Estate-admin feature settings, where unavailable plan features should be disabled

### PATCH `/admin/subscription-plans/:code`

Updates plan pricing, resident ranges, active status, custom-pricing flag, or the allowed feature list. This is super-admin-only; estate admins cannot add features to a plan.

Request:

```json
{
  "name": "Starter",
  "minResidents": 0,
  "maxResidents": 100,
  "monthlyPriceKobo": 25000000,
  "currency": "NGN",
  "customPricing": false,
  "description": "0-100 residents/units",
  "isActive": true,
  "features": ["RESIDENTS_VIEW", "VISITORS_VIEW", "VISITOR_PASSES_CREATE", "CHECKIN_SCAN"]
}
```

### POST `/admin/subscription-plans`

Creates a super-admin-owned plan. Use this for custom plans where super admin sets the price and included features.

Request:

```json
{
  "code": "CUSTOM_LUXURY",
  "name": "Luxury Custom",
  "minResidents": 301,
  "maxResidents": null,
  "monthlyPriceKobo": 150000000,
  "currency": "NGN",
  "customPricing": true,
  "description": "Custom plan controlled by super admin.",
  "isActive": true,
  "features": ["RESIDENTS_VIEW", "GUARDS_VIEW", "VISITORS_VIEW", "VISITOR_PASSES_CREATE", "CHECKIN_SCAN"]
}
```

### GET `/admin/sales-partners`

Super-admin-only list of sales admins with partner activation status, commission percentage, and assigned estates.

### GET `/admin/sales-partners/me`

Sales-admin WebApp profile endpoint. `profileLabel` is `Partner` only when super admin has activated earnings.

Success:

```json
{
  "id": "user-uuid",
  "name": "Sales Partner",
  "phone": "+2348000000005",
  "role": "SALES_ADMIN",
  "profileLabel": "Partner",
  "partnerProfile": {
    "enabled": true,
    "commissionPercent": 7.5,
    "notes": "Partner status enabled after super-admin review."
  },
  "assignedEstates": []
}
```

### PATCH `/admin/sales-partners/:salesAdminId`

Super-admin-only partner control for enabling/disabling earnings and setting commission percentage.

Request:

```json
{
  "enabled": true,
  "commissionPercent": 7.5,
  "notes": "Partner status enabled after super-admin review."
}
```

### GET `/admin/sales-partners/me/earnings`

Sales-admin WebApp earnings endpoint. Requires `SALES_PARTNER_EARNINGS_VIEW`.

### GET `/admin/sales-partners/:salesAdminId/earnings`

Super-admin earnings view for any sales admin partner.

### PATCH `/admin/estates/:id/sales-partner`

Assigns or clears the sales admin responsible for an estate. Super-admin-only.

Request:

```json
{
  "salesAdminId": "user-uuid"
}
```

### POST `/admin/estates/:estateId/admin-contact`

Queues or records a sales partner contact request for the estate admins assigned to that estate. Requires `SALES_PARTNER_ADMIN_CONTACT`; sales admins can contact only admins for their assigned estates.

Request:

```json
{
  "channel": "SMS",
  "message": "Please complete your subscription payment so we can activate your estate.",
  "metadata": {
    "reason": "PAYMENT_PENDING"
  }
}
```

### GET `/admin/payment-providers?estateId=...`

Lists global provider settings and estate override if provided.

### PATCH `/admin/payment-providers/:provider`

Enables or disables provider globally.

Request:

```json
{
  "enabled": true
}
```

### PATCH `/admin/estates/:estateId/payment-providers/:provider`

Sets estate-specific provider override.

Request:

```json
{
  "enabled": true
}
```

### GET `/admin/otp/config`

Shows OTP provider policy and provider configuration status. Secrets are not returned.

UI screens:
- Super-admin OTP settings

Success:

```json
{
  "provider": "TERMII",
  "enabledProviders": ["TERMII", "AFRICAS_TALKING"],
  "providerPriority": ["TERMII", "AFRICAS_TALKING"],
  "availableProviders": ["DEV", "TERMII", "AFRICAS_TALKING", "CUSTOM_HTTP"],
  "providers": [
    {
      "provider": "TERMII",
      "enabled": true,
      "priority": 1,
      "configured": true,
      "activePrimary": true
    }
  ],
  "expiresInSeconds": 300,
  "maxAttempts": 5
}
```

### PATCH `/admin/otp/config`

Updates OTP provider policy. Applies to all phone OTP login.

Request:

```json
{
  "enabledProviders": ["TERMII", "AFRICAS_TALKING"],
  "providerPriority": ["TERMII", "AFRICAS_TALKING", "CUSTOM_HTTP"],
  "ttlSeconds": 300,
  "codeLength": 6,
  "maxAttempts": 5,
  "exposeDevCode": false,
  "termiiApiKey": "secret",
  "termiiSenderId": "SmartEstate"
}
```

### GET/PATCH Admin Data Tables

These power super-admin global tables:

```text
GET   /admin/residents?estateId=
PATCH /admin/residents/:id
GET   /admin/guards?estateId=
PATCH /admin/guards/:id
GET   /admin/visitors?estateId=
PATCH /admin/visitors/:id
GET   /admin/visitor-passes?estateId=&status=
PATCH /admin/visitor-passes/:id
GET   /admin/visit-sessions?estateId=
GET   /admin/incidents?estateId=
PATCH /admin/incidents/:id
GET   /admin/audit-logs?estateId=
```

Use these for super-admin dashboards that need full-platform visibility.

## Suggested UI Modules

### Public Web

- Pricing: `GET /onboarding/subscription-plans`
- Estate admin signup: `POST /onboarding/estate-admin-signup`
- Payment provider selector: `GET /onboarding/payment-providers`
- Hosted checkout initialization: `POST /onboarding/estates/:estateId/payments/initialize`

### Super Admin Web

- Overview: `/admin/dashboard`
- Estate signup review: `/admin/estate-signups`, `/admin/estate-signups/:estateId/review`
- Payment confirmation: `/admin/estate-signups/:estateId/confirm-payment`
- Provider settings: `/admin/payment-providers`, `/admin/otp/config`
- Third-party keys: `/admin/integrations/templates`, `/admin/integrations/config`
- Subscription plan settings: `/admin/subscription-plans`
- Global records: admin list/update endpoints

### Estate Admin Web

- Residents: `/residents`
- Guards: `/guards`
- Visitors: `/visitors`
- Visitor passes: `/visitors/passes`
- Incidents: `/incidents`
- Notifications: `/notifications/send`

### Resident Mobile

- Login: `/auth/login`, `/auth/verify-otp`
- Visitor creation: `/visitors`
- Pass creation: `/visitors/passes`
- Pass list: `/visitors/passes`
- Pass revoke is not resident-enabled in current API

### Guard Mobile

- Login: `/auth/login`, `/auth/verify-otp`
- Offline bootstrap: `/offline/bootstrap`
- QR scan check-in: `/checkin/scan`
- Manual code check-in: `/checkin/manual`
- Checkout: `/checkin/checkout`
- Incident report: `/incidents`
- Offline sync: `/offline/sync`

## Frontend Implementation Notes

- Always keep `estateId` from the token for estate-scoped users.
- Super admin usually must choose an estate explicitly.
- Use `deviceId` consistently in guard mobile apps.
- Cache offline bootstrap data on guard devices.
- Convert `monthlyPriceKobo` to user-facing NGN by dividing by 100.
- Display `visitorCode` as a manual fallback even when QR is available.
- Treat `SUSPENDED`, `REJECTED`, and `PENDING` estates as blocked app states for normal users.
- Do not expose docs/sandbox links in public UI.
- Treat `docs/production-readiness-checklist.md` as the frontend release gate for WebApp and MobileApp.

