# Phase 1 — API Contract ## General Conventions - Base URL public: `https://api.halobestie.com` - Base URL internal: `https://internal.halobestie.com` - All requests: `Content-Type: application/json` - All authenticated requests: `Authorization: Bearer ` - All responses follow this envelope: ```json // Success { "success": true, "data": { ... } } // Error { "success": false, "error": { "code": "ERROR_CODE", "message": "Human readable message" } } ``` --- ## Error Codes | Code | HTTP | Description | |---|---|---| | `UNAUTHORIZED` | 401 | Missing or invalid Firebase JWT | | `FORBIDDEN` | 403 | Valid token but insufficient role/permission | | `NOT_FOUND` | 404 | Resource not found | | `VALIDATION_ERROR` | 422 | Request body/params failed validation | | `ACCOUNT_NOT_FOUND` | 404 | Phone number not registered as mitra | | `ACCOUNT_INACTIVE` | 403 | Mitra account is inactive | | `DISPLAY_NAME_REQUIRED` | 422 | Display name not provided | | `ALREADY_REGISTERED` | 409 | Anonymous user already has phone/social linked | | `INTERNAL_ERROR` | 500 | Unexpected server error | --- ## Public API (port 3000) ### Customer #### `POST /api/shared/customer/anonymous` Create an anonymous customer with a display name. **Auth:** None **Request:** ```json { "display_name": "Angin Malam" } ``` **Response `201`:** ```json { "success": true, "data": { "id": "uuid", "display_name": "Angin Malam", "is_anonymous": true, "created_at": "2026-04-04T00:00:00Z" } } ``` **Errors:** `DISPLAY_NAME_REQUIRED`, `VALIDATION_ERROR` --- #### `POST /api/shared/customer/link` Link phone OTP or social login to an existing anonymous customer. **Auth:** Firebase JWT (anonymous customer) **Request:** ```json { "customer_id": "uuid", "firebase_uid": "firebase_uid_from_auth" } ``` **Response `200`:** ```json { "success": true, "data": { "id": "uuid", "display_name": "Angin Malam", "is_anonymous": false, "phone": "+628xxxxxxxxxx", "created_at": "2026-04-04T00:00:00Z" } } ``` **Errors:** `UNAUTHORIZED`, `NOT_FOUND`, `ALREADY_REGISTERED` --- #### `POST /api/client/auth/verify` Verify Firebase JWT and return customer profile. Called after every login. **Auth:** Firebase JWT (customer) **Request:** _(empty body)_ **Response `200`:** ```json { "success": true, "data": { "id": "uuid", "display_name": "Angin Malam", "is_anonymous": false, "phone": "+628xxxxxxxxxx", "created_at": "2026-04-04T00:00:00Z" } } ``` **Errors:** `UNAUTHORIZED`, `NOT_FOUND` --- ### Mitra #### `POST /api/mitra/auth/verify` Verify Firebase JWT and return mitra profile. Called after OTP login. **Auth:** Firebase JWT (mitra) **Request:** _(empty body)_ **Response `200`:** ```json { "success": true, "data": { "id": "uuid", "display_name": "Dr. Budi", "phone": "+628xxxxxxxxxx", "is_active": true, "created_at": "2026-04-04T00:00:00Z" } } ``` **Errors:** `UNAUTHORIZED`, `ACCOUNT_NOT_FOUND`, `ACCOUNT_INACTIVE` --- ### Anonymity Config (read-only for apps) #### `GET /api/shared/config/anonymity` Get current anonymity setting. Apps poll this to decide whether to show force-register wall. **Auth:** Firebase JWT (customer) **Response `200`:** ```json { "success": true, "data": { "anonymity_enabled": true } } ``` --- ## Internal API (port 3001) > All internal routes require a valid Firebase JWT with `role` verified server-side. ### Auth #### `POST /internal/auth/verify` Verify Firebase JWT and return control center user profile with role and permissions. **Auth:** Firebase JWT (control center user) **Request:** _(empty body)_ **Response `200`:** ```json { "success": true, "data": { "id": "uuid", "email": "admin@halobestie.com", "display_name": "Admin", "role": { "id": "uuid", "name": "super_admin", "permissions": { "mitra": ["create", "read", "update", "delete"], "control_center_users": ["create", "read", "update", "delete"], "config": ["read", "update"] } } } } ``` **Errors:** `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND` --- ### Mitra Management #### `POST /internal/mitras` Create a new mitra record. **Auth:** Firebase JWT (control center user, requires `mitra:create` permission) **Request:** ```json { "phone": "+628xxxxxxxxxx", "display_name": "Dr. Budi" } ``` **Response `201`:** ```json { "success": true, "data": { "id": "uuid", "phone": "+628xxxxxxxxxx", "display_name": "Dr. Budi", "is_active": false, "created_at": "2026-04-04T00:00:00Z" } } ``` **Errors:** `UNAUTHORIZED`, `FORBIDDEN`, `VALIDATION_ERROR` --- #### `GET /internal/mitras` List all mitras. **Auth:** Firebase JWT (control center user, requires `mitra:read` permission) **Query params:** - `page` (default: 1) - `limit` (default: 20) - `is_active` (optional: `true` | `false`) **Response `200`:** ```json { "success": true, "data": { "items": [ { "id": "uuid", "phone": "+628xxxxxxxxxx", "display_name": "Dr. Budi", "is_active": true, "created_at": "2026-04-04T00:00:00Z" } ], "total": 1, "page": 1, "limit": 20 } } ``` --- #### `PATCH /internal/mitras/:id/status` Activate or deactivate a mitra. **Auth:** Firebase JWT (control center user, requires `mitra:update` permission) **Request:** ```json { "is_active": true } ``` **Response `200`:** ```json { "success": true, "data": { "id": "uuid", "is_active": true } } ``` **Errors:** `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `VALIDATION_ERROR` --- ### Control Center User Management #### `POST /internal/control-center-users` Create a new control center user. **Auth:** Firebase JWT (requires `control_center_users:create` permission) **Request:** ```json { "email": "operator@halobestie.com", "display_name": "Operator 1", "role_id": "uuid" } ``` **Response `201`:** ```json { "success": true, "data": { "id": "uuid", "email": "operator@halobestie.com", "display_name": "Operator 1", "role": { "id": "uuid", "name": "operator" }, "created_at": "2026-04-04T00:00:00Z" } } ``` **Errors:** `UNAUTHORIZED`, `FORBIDDEN`, `VALIDATION_ERROR` --- #### `GET /internal/control-center-users` List all control center users. **Auth:** Firebase JWT (requires `control_center_users:read` permission) **Query params:** - `page` (default: 1) - `limit` (default: 20) **Response `200`:** ```json { "success": true, "data": { "items": [ { "id": "uuid", "email": "operator@halobestie.com", "display_name": "Operator 1", "role": { "id": "uuid", "name": "operator" }, "created_at": "2026-04-04T00:00:00Z" } ], "total": 1, "page": 1, "limit": 20 } } ``` --- ### Roles #### `GET /internal/roles` List all roles. **Auth:** Firebase JWT (requires `control_center_users:read` permission) **Response `200`:** ```json { "success": true, "data": [ { "id": "uuid", "name": "super_admin", "permissions": { "mitra": ["create", "read", "update", "delete"], "control_center_users": ["create", "read", "update", "delete"], "config": ["read", "update"] } } ] } ``` --- ### Config #### `GET /internal/config/anonymity` Get anonymity setting. **Auth:** Firebase JWT (requires `config:read` permission) **Response `200`:** ```json { "success": true, "data": { "anonymity_enabled": true } } ``` --- #### `PATCH /internal/config/anonymity` Toggle anonymity setting. **Auth:** Firebase JWT (requires `config:update` permission) **Request:** ```json { "anonymity_enabled": false } ``` **Response `200`:** ```json { "success": true, "data": { "anonymity_enabled": false } } ``` **Errors:** `UNAUTHORIZED`, `FORBIDDEN`, `VALIDATION_ERROR`