# 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 <firebase_jwt>`
- 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`
