# Shop Management

**Author**: Josh S. Sakweli, Backend Lead Team  
**Last Updated**: 2026-07-07  
**Version**: v2.1

**Short Description**: The Shop Management API provides endpoints for creating, managing, and retrieving shop information on the NextGate platform. Covers shop registration, updates, approvals, WABA (WhatsApp Business) integration, AI chatbot toggling, and conversation history.

**Hints**:
- All shop endpoints use the prefix `api/v1/e-commerce/shops`
- Shop creation requires authentication; public users can only read
- Pagination uses 1-based page numbering
- Shop approval operations require `ROLE_SUPER_ADMIN` or `ROLE_STAFF_ADMIN`
- Featured shops are randomized on each request
- Rating and review data is automatically included in shop responses (read-only — managed by separate review service)
- WABA = WhatsApp Business Account integration for the shop's AI-powered chatbot

**Shop media (logo, banner) goes through FileThunder — no raw URLs are sent by the client**, same pipeline used by product media elsewhere in the API:
1. Client calls `POST api/v1/files/request-upload` with `context: "SHOP_LOGO"` or `"SHOP_BANNER"` → gets back a presigned upload URL and a `fileId`
2. Client `PUT`s the raw file bytes directly to that URL
3. Client sends that `fileId` as `logoFileId`/`bannerFileId` when creating/updating the shop — validated at request time against the FileThunder Redis cache (ownership + expected context), not necessarily fully processed yet
4. FileThunder finishes processing (virus scan + variant generation) asynchronously and webhooks back into `ShopMediaUpdater.onFileReady`, which upgrades the stored `shopLogoMedia`/`shopBannerMedia` to `status: READY` with resolved `variants`
- There is **no shop image gallery** (`shopImages`) field — only a single `shopLogoMedia` and single `shopBannerMedia`, each a `ShopMedia` object (`fileId`, `status`, `variants`, `mimeType`). URLs are assembled fresh on every read via `FileThunderUrlAssembler`, never stored as raw strings
- Owner/user avatars follow the same rule: `ownerProfileMedia` on `ShopResponse` (single-shop detail view) is a full `ProfileMedia` object (`fileId`, `status`, `variants`, `mimeType`) resolved via `FileThunderUrlAssembler.assembleProfileMedia()`. `userProfileMedia` on review responses and on subscriber responses (list/card contexts) is instead the lean `UserProfilePrimaryMedia` (`fileId`, `status`, `thumbUrl`, `mimeType`) resolved via `FileThunderUrlAssembler.getUserProfilePrimaryMedia()` — a single resolved thumbnail URL instead of the full variants map. Neither is ever a raw `avatarUrl`/`userAvatarUrl` string

---

## Standard Response Format

```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Operation completed successfully",
  "action_time": "2026-05-19T10:30:45",
  "data": { }
}
```

Error responses follow the same envelope with `"success": false` and `"data"` set to the error message string.

---

## Endpoints

## 1. Create Shop
**Endpoint**: <span style="background-color: #28a745; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">POST</span> `api/v1/e-commerce/shops`

**Access Level**: 🔒 Protected

**Request Body**:
| Parameter | Type | Required | Description | Validation |
|-----------|------|----------|-------------|------------|
| shopName | string | Yes | Name of the shop | Min: 2, Max: 100 chars |
| shopDescription | string | Yes | Detailed description | Max: 1000 chars |
| logoFileId | UUID | Yes | FileThunder file ID for the shop logo | Uploaded via `POST api/v1/files/request-upload` with context `SHOP_LOGO`, owned by the caller |
| phoneNumber | string | Yes | Contact phone | Pattern: `^\+?[0-9]{10,15}$` |
| city | string | Yes | City | Min: 2, Max: 50 chars |
| region | string | Yes | Region/state | Min: 2, Max: 50 chars |
| bannerFileId | UUID | No | FileThunder file ID for the shop banner | Uploaded with context `SHOP_BANNER` |
| email | string | No | Contact email | Valid email, max 100 chars |
| countryCode | string | No | Country code | Max: 3 chars, Default: `"TZ"` |
| streetAddress | string | No | Street address | Max: 255 chars |
| landmark | string | No | Landmark / location notes | Max: 300 chars |
| latitude | decimal | No | GPS latitude | Range: -90.0 to 90.0 |
| longitude | decimal | No | GPS longitude | Range: -180.0 to 180.0 |

**Note**: there is no `shopImages` field — shops only ever have a `shopLogoMedia` and a `shopBannerMedia`, never a gallery.

**Request JSON Sample**:
```json
{
  "shopName": "Mama Lucy's Restaurant",
  "shopDescription": "Authentic Tanzanian cuisine in the heart of Dar es Salaam",
  "logoFileId": "f77e4567-e89b-12d3-a456-426614174777",
  "phoneNumber": "+255123456789",
  "city": "Dar es Salaam",
  "region": "Dar es Salaam",
  "bannerFileId": "f88e4567-e89b-12d3-a456-426614174888",
  "email": "info@mamalucy.co.tz",
  "countryCode": "TZ",
  "streetAddress": "Msimbazi Street, Block 45",
  "landmark": "Near the main bus stop",
  "latitude": -6.7924,
  "longitude": 39.2083
}
```

**Response JSON Sample** (`ShopResponse`):
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Shop created successfully",
  "action_time": "2026-05-19T10:30:45",
  "data": {
    "shopId": "123e4567-e89b-12d3-a456-426614174000",
    "shopName": "Mama Lucy's Restaurant",
    "shopSlug": "mama-lucys-restaurant",
    "shopDescription": "Authentic Tanzanian cuisine...",
    "shopLogoMedia": {
      "fileId": "f77e4567-e89b-12d3-a456-426614174777",
      "status": "PROCESSING",
      "variants": null,
      "mimeType": "image/jpeg"
    },
    "shopBannerMedia": {
      "fileId": "f88e4567-e89b-12d3-a456-426614174888",
      "status": "PROCESSING",
      "variants": null,
      "mimeType": "image/jpeg"
    },
    "ownerId": "456e7890-e89b-12d3-a456-426614174001",
    "ownerName": "Lucy Mwalimu",
    "ownerProfileMedia": {
      "fileId": "p11e4567-e89b-12d3-a456-426614174p11",
      "status": "READY",
      "variants": {
        "medium": "https://cdn.example.com/profiles/owner-456/medium.jpg"
      },
      "mimeType": "image/jpeg"
    },
    "status": "ACTIVE",
    "phoneNumber": "+255123456789",
    "email": "info@mamalucy.co.tz",
    "streetAddress": "Msimbazi Street, Block 45",
    "city": "Dar es Salaam",
    "region": "Dar es Salaam",
    "countryCode": "TZ",
    "latitude": -6.7924,
    "longitude": 39.2083,
    "landmark": "Near the main bus stop",
    "isVerified": false,
    "verificationBadge": null,
    "trustScore": 0.00,
    "lastSeenTime": null,
    "isApproved": true,
    "createdAt": "2026-05-19T10:30:45",
    "updatedAt": "2026-05-19T10:30:45",
    "approvedAt": null,
    "averageRating": null,
    "totalRatings": 0,
    "totalActiveReviews": 0,
    "reviews": [],
    "isSubscribed": false,
    "subscriberCount": 0,
    "productCount": 0
  }
}
```

**Notes**:
- `shopLogoMedia`/`shopBannerMedia` are resolved from the FileThunder Redis cache at request time — the file must have been requested via `POST api/v1/files/request-upload` (matching context), be owned by the caller, and be at least `PROCESSING`. They start out with `variants: null` and get upgraded to `status: "READY"` with resolved CDN URLs in `variants` asynchronously once FileThunder finishes processing, via a webhook into `ShopMediaUpdater.onFileReady` — re-fetch the shop to see the upgrade
- Shops are created directly as `status: "ACTIVE"` and `isApproved: true` — there's currently no `PENDING`-on-create/manual-approval-required flow in code, despite [9. Approve/Reject Shop](#9-approve--reject-shop) existing for admins to flip `isApproved`

**Error Responses**:
- `400`: Shop name already exists, or `logoFileId`/`bannerFileId` was never uploaded (upload expired or never started), or does not belong to the caller
- `401`: Authentication required
- `422`: Validation errors

---

## 2. Get All Shops
**Endpoint**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">GET</span> `api/v1/e-commerce/shops/all`

**Access Level**: 🌐 Public

Returns a list of all shops with summary info and top 5 reviews per shop.

---

## 3. Get All Shops (Paginated)
**Endpoint**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">GET</span> `api/v1/e-commerce/shops/all-paged`

**Access Level**: 🌐 Public

**Query Parameters**:
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| page | integer | 1 | Page number (1-based) |
| size | integer | 10 | Items per page (max: 100) |

**Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Shops retrieved successfully",
  "action_time": "2026-05-19T10:30:45",
  "data": {
    "shops": [ "...ShopSummary fields..." ],
    "currentPage": 1,
    "pageSize": 10,
    "totalElements": 150,
    "totalPages": 15,
    "hasNext": true,
    "hasPrevious": false,
    "isFirst": true,
    "isLast": false
  }
}
```

---

## 4. Update Shop
**Endpoint**: <span style="background-color: #ffc107; color: black; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">PUT</span> `api/v1/e-commerce/shops/{shopId}`

**Access Level**: 🔒 Protected (Shop Owner only)

**Path Parameters**:
| Parameter | Type | Description |
|-----------|------|-------------|
| shopId | UUID | ID of the shop |

All request body fields are optional — only provided fields are updated:

| Parameter       | Type    | Validation                                                                                              |
| --------------- | ------- | ------------------------------------------------------------------------------------------------------- |
| shopName        | string  | Min: 2, Max: 100 chars — slug is regenerated only if the name actually changed                          |
| shopDescription | string  | Max: 1000 chars                                                                                         |
| logoFileId      | UUID    | Replaces `shopLogoMedia` — same FileThunder validation as Create (context `SHOP_LOGO`, owned by caller) |
| bannerFileId    | UUID    | Replaces `shopBannerMedia` — context `SHOP_BANNER`                                                      |
| phoneNumber     | string  | `^\+?[0-9]{10,15}$`                                                                                     |
| email           | string  | Valid email                                                                                             |
| streetAddress   | string  | Max: 255 chars                                                                                          |
| city            | string  | Min: 2, Max: 50 chars                                                                                   |
| region          | string  | Min: 2, Max: 50 chars                                                                                   |
| countryCode     | string  | Max: 3 chars                                                                                            |
| latitude        | decimal | -90.0 to 90.0                                                                                           |
| longitude       | decimal | -180.0 to 180.0                                                                                         |
| landmark        | string  | Max: 300 chars                                                                                          |

**Notes**:
- There is no `shopImages` field here either, and no way to *remove* a logo/banner via this endpoint (no `clearLogo`/`clearBanner` flag) — sending a new `logoFileId`/`bannerFileId` replaces the existing one, but there's no null-out path
- Unlike product media, a replaced `shopLogoMedia`/`shopBannerMedia` is **not** merged/preserved — it's a straight overwrite with a freshly Redis-resolved `ShopMedia`

**Response**: Full `ShopResponse` (same shape as [Create Shop](#1-create-shop) response)

**Error Responses**:
- `400`: Not the shop owner, shop is deleted, or `logoFileId`/`bannerFileId` was never uploaded/doesn't belong to the caller
- `401`: Authentication required
- `404`: Shop not found

---

## 5. Get Shop by ID (Summary)
**Endpoint**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">GET</span> `api/v1/e-commerce/shops/{shopId}`

**Access Level**: 🌐 Public

Returns `ShopSummaryListResponse` — public-facing fields including top 5 reviews and rating.

---

## 6. Get Shop by ID (Detailed)
**Endpoint**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">GET</span> `api/v1/e-commerce/shops/{shopId}/detailed`

**Access Level**: 🔒 Protected (Shop Owner or Admin)

Returns full `ShopResponse` including all reviews and management fields.

---

## 7. Get My Shops
**Endpoint**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">GET</span> `api/v1/e-commerce/shops/my-shops`

**Access Level**: 🔒 Protected

Returns all shops owned by the authenticated user (`ShopSummaryListResponse` list).

---

## 8. Get My Shops (Paginated)
**Endpoint**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">GET</span> `api/v1/e-commerce/shops/my-shops-paged`

**Access Level**: 🔒 Protected

**Query Parameters**:
| Parameter | Default | Description |
|-----------|---------|-------------|
| page | 1 | Page number (1-based) |
| size | 10 | Items per page (max: 100) |

---

## 9. Approve / Reject Shop
**Endpoint**: <span style="background-color: #6f42c1; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">PATCH</span> `api/v1/e-commerce/shops/{shopId}/approve-shop`

**Access Level**: 🔒 Protected (`ROLE_SUPER_ADMIN` or `ROLE_STAFF_ADMIN`)

**Query Parameters**:
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| approve | boolean | Yes | `true` to approve, `false` to reject |

**Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Shop approval status changed successfully",
  "action_time": "2026-05-19T10:30:45",
  "data": {
    "shopId": "123e4567-e89b-12d3-a456-426614174000",
    "shopName": "Mama Lucy's Restaurant",
    "isApproved": true,
    "approvedAt": "2026-05-19T10:30:45"
  }
}
```

---

## 10. Get Featured Shops
**Endpoint**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">GET</span> `api/v1/e-commerce/shops/featured`

**Access Level**: 🌐 Public

Returns up to 20 randomly selected featured shops (`ShopSummaryListResponse` list).

---

## 11. Get Featured Shops (Paginated)
**Endpoint**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">GET</span> `api/v1/e-commerce/shops/featured-paged`

**Access Level**: 🌐 Public

**Query Parameters**:
| Parameter | Default | Description |
|-----------|---------|-------------|
| page | 1 | Page number |
| size | 10 | Items per page (max: 100) |

---

## 12. Search Shops
**Endpoint**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">GET</span> `api/v1/e-commerce/shops/search`

**Access Level**: 🌐 Public

**Query Parameters**:
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| q | string | No | — | Search query |
| page | integer | No | 1 | Page number |
| size | integer | No | 10 | Items per page |

**Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Shops retrieved successfully",
  "action_time": "2026-05-19T10:30:45",
  "data": {
    "content": [ "...ShopSearchResponse fields..." ],
    "totalElements": 8,
    "totalPages": 1,
    "currentPage": 1,
    "pageSize": 10,
    "hasNext": false,
    "hasPrevious": false
  }
}
```

---

## 13. Get Shop Summary Stats
**Endpoint**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">GET</span> `api/v1/e-commerce/shops/{shopId}/summary-stats`

**Access Level**: 🌐 Public

Returns aggregated review and rating statistics for a shop, including per-user activity.

**Response JSON Sample**:
```json
{
  "success": true,
  "httpStatus": "OK",
  "message": "Shop summary stats retrieved successfully",
  "data": {
    "shopId": "123e4567-e89b-12d3-a456-426614174000",
    "shopName": "Mama Lucy's Restaurant",
    "averageRating": 4.5,
    "totalRatings": 25,
    "ratingDistribution": { "1": 1, "2": 2, "3": 5, "4": 7, "5": 10 },
    "totalReviews": 15,
    "activeReviews": 12,
    "hiddenReviews": 2,
    "flaggedReviews": 1,
    "userActivities": [
      {
        "userId": "user-123",
        "userName": "John Doe",
        "feedbackId": "rev-123",
        "reviewText": "Amazing food and service!",
        "reviewStatus": "ACTIVE",
        "ratingValue": 5,
        "date": "2026-05-19T14:30:00",
        "hasReview": true,
        "hasRating": true
      }
    ]
  }
}
```

---

## 14. WABA (WhatsApp Business) Integration

WABA allows shops to receive and respond to WhatsApp customer messages via an AI-powered chatbot. The flow is: shop registers a WABA → admin approves with Meta credentials → shop toggles AI on/off and monitors conversation history.

**Base path for all WABA endpoints**: `api/v1/e-commerce/shops/{shopId}/waba`

**Shared Path Parameter**:
| Parameter | Type | Description |
|-----------|------|-------------|
| shopId | UUID | ID of the shop |

---

### 14a. Register WABA
**Purpose**: Shop owner submits a WhatsApp number and display name to begin WABA registration.

**Endpoint**: <span style="background-color: #28a745; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">POST</span> `api/v1/e-commerce/shops/{shopId}/waba/register`

**Access Level**: 🔒 Protected (Shop Owner)

**Request Body**:
| Parameter | Type | Required | Validation |
|-----------|------|----------|------------|
| phoneNumber | string | Yes | Max: 20 chars |
| displayName | string | Yes | Max: 100 chars |

**Request JSON Sample**:
```json
{
  "phoneNumber": "+255712345678",
  "displayName": "Mama Lucy's Restaurant"
}
```

---

### 14b. Approve WABA
**Purpose**: Admin approves a pending WABA registration by supplying Meta WABA credentials.

**Endpoint**: <span style="background-color: #6f42c1; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">PATCH</span> `api/v1/e-commerce/shops/{shopId}/waba/approve`

**Access Level**: 🔒 Protected (`ROLE_SUPER_ADMIN` or `ROLE_STAFF_ADMIN`)

**Request Body**:
| Parameter | Type | Required | Validation |
|-----------|------|----------|------------|
| wabaId | string | Yes | Max: 64 chars (Meta WABA ID) |
| phoneNumberId | string | Yes | Max: 64 chars (Meta Phone Number ID) |
| phoneNumber | string | Yes | Max: 20 chars |

---

### 14c. Resubmit WABA
**Purpose**: Shop owner resubmits a rejected or pending WABA with corrected info.

**Endpoint**: <span style="background-color: #6f42c1; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">PATCH</span> `api/v1/e-commerce/shops/{shopId}/waba/resubmit`

**Access Level**: 🔒 Protected (Shop Owner)

**Request Body**:
| Parameter | Type | Validation |
|-----------|------|------------|
| phoneNumber | string | Max: 20 chars |
| displayName | string | Max: 100 chars |

---

### 14d. Admin Update WABA
**Purpose**: Admin updates Meta credentials on an existing WABA account.

**Endpoint**: <span style="background-color: #6f42c1; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">PATCH</span> `api/v1/e-commerce/shops/{shopId}/waba/admin-update`

**Access Level**: 🔒 Protected (`ROLE_SUPER_ADMIN` or `ROLE_STAFF_ADMIN`)

Same request body as [Resubmit WABA](#14c-resubmit-waba).

---

### 14e. Update WABA Status
**Purpose**: Admin changes the status of a WABA account (e.g., suspend or reactivate).

**Endpoint**: <span style="background-color: #6f42c1; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">PATCH</span> `api/v1/e-commerce/shops/{shopId}/waba/status`

**Access Level**: 🔒 Protected (`ROLE_SUPER_ADMIN` or `ROLE_STAFF_ADMIN`)

**Query Parameters**:
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| status | ShopWabaStatus | Yes | `PENDING`, `ACTIVE`, `SUSPENDED`, `REJECTED` |

---

### 14f. Toggle AI Chatbot
**Purpose**: Shop owner enables or disables the AI chatbot for their WABA.

**Endpoint**: <span style="background-color: #6f42c1; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">PATCH</span> `api/v1/e-commerce/shops/{shopId}/waba/toggle-ai`

**Access Level**: 🔒 Protected (Shop Owner)

**Query Parameters**:
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| enabled | boolean | Yes | `true` to enable, `false` to disable |

**Response JSON Sample**:
```json
{
  "success": true,
  "message": "AI enabled successfully",
  "data": {
    "shopId": "...",
    "aiEnabled": true,
    "updatedAt": "2026-05-19T11:00:00"
  }
}
```

---

### 14g. Get WABA Conversations
**Purpose**: Retrieves paginated WhatsApp conversation sessions for the shop.

**Endpoint**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">GET</span> `api/v1/e-commerce/shops/{shopId}/waba/conversations`

**Access Level**: 🔒 Protected (Shop Owner or Admin)

**Query Parameters**:
| Parameter | Default | Description |
|-----------|---------|-------------|
| page | 1 | Page number |
| size | 10 | Items per page |

**Response JSON Sample**:
```json
{
  "success": true,
  "message": "Conversations retrieved",
  "data": {
    "content": [ "...WabaSessionResponse fields..." ],
    "currentPage": 1,
    "pageSize": 10,
    "totalElements": 42,
    "totalPages": 5,
    "hasNext": true,
    "hasPrevious": false
  }
}
```

---

### 14h. Get Session Messages
**Purpose**: Retrieves paginated messages within a specific conversation session.

**Endpoint**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">GET</span> `api/v1/e-commerce/shops/{shopId}/waba/conversations/{sessionId}/messages`

**Access Level**: 🔒 Protected (Shop Owner or Admin)

**Additional Path Parameter**:
| Parameter | Type | Description |
|-----------|------|-------------|
| sessionId | UUID | ID of the conversation session |

**Query Parameters**:
| Parameter | Default | Description |
|-----------|---------|-------------|
| page | 1 | Page number |
| size | 20 | Items per page |

---

### 14i. Get Session Messages by Date Range
**Purpose**: Retrieves messages in a session filtered by date range.

**Endpoint**: <span style="background-color: #007bff; color: white; padding: 4px 8px; border-radius: 4px; font-family: monospace; font-size: 12px; font-weight: bold;">GET</span> `api/v1/e-commerce/shops/{shopId}/waba/conversations/{sessionId}/messages/by-date`

**Access Level**: 🔒 Protected (Shop Owner or Admin)

**Additional Path Parameter**:
| Parameter | Type | Description |
|-----------|------|-------------|
| sessionId | UUID | ID of the conversation session |

**Query Parameters**:
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| from | LocalDateTime | Yes | Start datetime (ISO 8601) |
| to | LocalDateTime | Yes | End datetime (ISO 8601) |
| page | integer | No | Default: 1 |
| size | integer | No | Default: 20 |

---

## Quick Reference

### Enums

**ShopStatus**: `PENDING`, `ACTIVE`, `SUSPENDED`, `CLOSED`, `UNDER_REVIEW`

**ShopType**: `PHYSICAL`, `ONLINE`, `HYBRID`

**VerificationBadge**: `BRONZE`, `SILVER`, `GOLD`, `PREMIUM`

**ShopWabaStatus**: `PENDING`, `ACTIVE`, `SUSPENDED`, `REJECTED`

### Error Response Codes

| Code  | Meaning                                          |
| ----- | ------------------------------------------------ |
| `400` | Business logic violation or item already exists  |
| `401` | Authentication required or token invalid/expired |
| `403` | Insufficient permissions                         |
| `404` | Resource not found                               |
| `422` | Field-level validation errors                    |

**Validation Error (422)**:
```json
{
  "success": false,
  "httpStatus": "UNPROCESSABLE_ENTITY",
  "message": "Validation failed",
  "data": {
    "shopName": "Shop name must be between 2 and 100 characters",
    "phoneNumber": "Phone number must be between 10-15 digits and may start with +"
  }
}
```

### Access Control Summary
| Role                                    | Capabilities                                                             |
| --------------------------------------- | ------------------------------------------------------------------------ |
| Public                                  | Read shops, search, featured, summary stats                              |
| Authenticated user                      | All public + create shop, view own shops                                 |
| Shop Owner                              | All authenticated + update own shop, WABA management, view conversations |
| `ROLE_SUPER_ADMIN` / `ROLE_STAFF_ADMIN` | All + approve/reject shops, approve/update/status WABA                   |

### WABA Registration Flow
```
1. POST /{shopId}/waba/register       — shop owner submits phone + display name
2. PATCH /{shopId}/waba/approve       — admin supplies Meta wabaId + phoneNumberId
3. PATCH /{shopId}/waba/toggle-ai     — shop owner enables AI chatbot
4. GET  /{shopId}/waba/conversations  — shop owner monitors customer chats
```