# MeeChat Mobile API Documentation

**Base URL:** `https://yourserver.com/api/mobile`  
**Version:** 1.0.0  
**Last Updated:** 2026-01-03

---

## Table of Contents

1. [Overview](#overview)
2. [Authentication](#authentication)
3. [Error Handling](#error-handling)
4. [Rate Limiting](#rate-limiting)
5. [Endpoints](#endpoints)
   - [Auth](#auth-endpoints)
   - [Conversations](#conversation-endpoints)
   - [Messages](#message-endpoints)
   - [Media](#media-endpoints)
   - [Templates](#template-endpoints)
   - [Agent Management](#agent-management-endpoints)
   - [Dashboard](#dashboard-endpoints)

---

## Overview

MeeChat Mobile API provides REST endpoints for building mobile applications (Android/iOS) that enables **Client** and **Agent** roles to manage WhatsApp conversations.

### Supported Roles

| Role | Description |
|------|-------------|
| `client` | Business owner - can view all conversations across owned WABAs |
| `agent` | Customer service - can view assigned conversations and claim unassigned ones |

### Response Format

All API responses follow this structure:

```json
{
  "success": true,
  "message": "Operation successful",
  "data": { ... }
}
```

Error responses:

```json
{
  "success": false,
  "message": "Error description",
  "error_code": "ERROR_CODE",
  "errors": { ... }  // Validation errors (optional)
}
```

---

## Authentication

### Authentication Flow

```
┌───────────┐          ┌──────────────┐          ┌─────────────┐
│  Mobile   │  login   │   Backend    │  verify  │   Database  │
│    App    │─────────►│   (Laravel)  │─────────►│   (MySQL)   │
└───────────┘          └──────────────┘          └─────────────┘
     │                        │
     │   token + user data    │
     │◄───────────────────────│
     │                        │
     │   Authorization: Bearer <token>
     │─────────────────────────────────────►  All subsequent requests
```

### Token Type

Uses **Laravel Sanctum** for API authentication. Tokens are:
- Plain text bearer tokens
- No expiration by default (configurable)
- Device-specific (one token per device)

### Headers Required

```http
Authorization: Bearer <your_access_token>
Accept: application/json
Content-Type: application/json
```

---

## Error Handling

### HTTP Status Codes

| Code | Description |
|------|-------------|
| 200 | Success |
| 201 | Created |
| 400 | Bad Request - Invalid parameters |
| 401 | Unauthorized - Invalid or expired token |
| 403 | Forbidden - Insufficient permissions |
| 404 | Not Found |
| 422 | Validation Error |
| 429 | Too Many Requests - Rate limited |
| 500 | Internal Server Error |

### Error Codes

| Code | Description |
|------|-------------|
| `VALIDATION_FAILED` | Input validation error |
| `UNAUTHORIZED` | Authentication required |
| `FORBIDDEN` | Permission denied |
| `NOT_FOUND` | Resource not found |
| `SERVICE_WINDOW_EXPIRED` | 24h WhatsApp service window expired |
| `WABA_INACTIVE` | WABA pool is not active |
| `UPLOAD_FAILED` | Media upload failed |
| `RATE_LIMITED` | Too many requests |

---

## Rate Limiting

| Scope | Limit | Window |
|-------|-------|--------|
| User (global) | 60 requests | 1 minute |
| Per conversation | 30 requests | 1 minute |
| Media upload | 10 uploads | 1 minute |

Headers returned on rate-limited requests:

```http
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
Retry-After: 30
```

---

## Endpoints

---

### Auth Endpoints

#### POST `/auth/login`

Login and receive access token.

**Request:**

```json
{
  "username": "user@example.com",
  "password": "your_password",
  "device_type": "android",  // "android" or "ios"
  "device_name": "Samsung Galaxy S21",
  "device_id": "unique_device_id",
  "fcm_token": "firebase_cloud_messaging_token"  // optional
}
```

**Response (200):**

```json
{
  "success": true,
  "message": "Login successful",
  "data": {
    "token": "1|abcdef123456...",
    "token_type": "bearer",
    "user": {
      "id": 1,
      "name": "John Doe",
      "username": "user@example.com",
      "role": "client",
      "avatar_url": null
    },
    "client": {
      "id": 1,
      "name": "PT Example",
      "balance": "150000.00"
    },
    "agent": null,
    "permissions": {
      "can_send_template": true,
      "can_send_media": true,
      "can_view_unassigned": false
    }
  }
}
```

**Response for Agent role:**

```json
{
  "success": true,
  "data": {
    "token": "2|xyz789...",
    "user": { ... },
    "client": {
      "id": 1,
      "name": "PT Example"
    },
    "agent": {
      "id": 5,
      "name": "Agent Name",
      "can_view_unassigned": true
    }
  }
}
```

**Errors:**

| Code | Status | Description |
|------|--------|-------------|
| 401 | `INVALID_CREDENTIALS` | Email or password incorrect |
| 403 | `ROLE_NOT_ALLOWED` | User role not allowed for mobile access |
| 403 | `AGENT_INACTIVE` | Agent account is not active |

---

#### POST `/auth/logout`

Logout and revoke current token.

**Headers:** `Authorization: Bearer <token>`

**Response (200):**

```json
{
  "success": true,
  "message": "Logged out successfully"
}
```

---

#### GET `/auth/me`

Get current authenticated user profile.

**Headers:** `Authorization: Bearer <token>`

**Response (200):**

```json
{
  "success": true,
  "data": {
    "user": {
      "id": 1,
      "name": "John Doe",
      "username": "user@example.com",
      "role": "client"
    },
    "client": {
      "id": 1,
      "name": "PT Example",
      "balance": "150000.00",
      "phone": "628123456789"
    },
    "agent": null,
    "waba_pool_ids": [1, 2, 3]
  }
}
```

**Response for Agent role:**

```json
{
  "success": true,
  "data": {
    "user": {
      "id": 2,
      "name": "Agent Name",
      "username": "agent@example.com",
      "role": "agent"
    },
    "client": {
      "id": 1,
      "name": "PT Example",
      "balance": "150000.00"
    },
    "agent": {
      "id": 5,
      "name": "Agent Name",
      "status": "active",
      "canViewUnassigned": true,
      "maxConcurrentChats": 10,
      "openChatsCount": 3
    },
    "waba_pool_ids": [1, 2]
  }
}
```

> **Note:** `waba_pool_ids` contains IDs of active WABA pools associated with the client. Returns empty array `[]` if no WABA pools are assigned.

---

#### POST `/auth/refresh`

Refresh token (creates new token, revokes old one).

**Headers:** `Authorization: Bearer <token>`

**Response (200):**

```json
{
  "success": true,
  "data": {
    "token": "3|new_token...",
    "token_type": "bearer"
  }
}
```

---

#### POST `/auth/device/register`

Register device for push notifications.

**Headers:** `Authorization: Bearer <token>`

**Request:**

```json
{
  "device_id": "unique_device_identifier",
  "device_type": "android",
  "device_name": "Samsung Galaxy S21",
  "fcm_token": "firebase_cloud_messaging_token",
  "app_version": "1.0.0"
}
```

**Response (200):**

```json
{
  "success": true,
  "message": "Device registered successfully",
  "data": {
    "device_id": "unique_device_identifier"
  }
}
```

---

#### DELETE `/auth/device/{device_id}`

Unregister device from push notifications.

**Headers:** `Authorization: Bearer <token>`

**Response (200):**

```json
{
  "success": true,
  "message": "Device unregistered successfully"
}
```

---

#### POST `/broadcasting/auth`

Authorize Pusher private channel subscription.

**Headers:** `Authorization: Bearer <token>`

**Request:**

```json
{
  "socket_id": "123456.789",
  "channel_name": "private-conversation.123"
}
```

**Response (200):**

```json
{
  "auth": "pusher_auth_signature"
}
```

---

### Conversation Endpoints

#### GET `/conversations`

List conversations accessible by the authenticated user.

**Headers:** `Authorization: Bearer <token>`

**Query Parameters:**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `unread_only` | boolean | false | Show only unread conversations |
| `assigned_to_me` | boolean | false | (Agent only) Show only assigned to me |
| `status` | string | null | Filter by: `open`, `closed` |
| `only_contacts` | boolean | false | Show only conversations from contacts (same as client inbox filter) |
| `search` | string | null | Search by customer name, number, contact name, or message content |
| `cursor` | string | null | Pagination cursor (base64) |
| `limit` | integer | 20 | Items per page (max 50) |

**Response (200):**

```json
{
  "success": true,
  "data": {
    "conversations": [
      {
        "id": 123,
        "customer_number": "628123456789",
        "customer_name": "Customer Name",
        "customer_display": "+62 812-3456-789",
        "status": "open",
        "unread_count": 3,
        "last_message": {
          "body": "Preview of last message...",
          "type": "text",
          "timestamp": "2025-12-25T10:30:00Z",
          "direction": "incoming"
        },
        "waba_pool": {
          "id": 1,
          "account_name": "Business Account",
          "phone_number": "628111222333"
        },
        "agent": {
          "id": 5,
          "name": "Agent Name"
        },
        "is_assigned": true,
        "is_mine": true,
        "service_window": {
          "is_open": true,
          "expires_at": "2025-12-25T22:30:00Z",
          "requires_template": false
        },
        "last_message_at": "2025-12-25T10:30:00Z"
      }
    ],
    "meta": {
      "has_more": true,
      "next_cursor": "eyJpZCI6MTAwfQ==",
      "total_count": 150
    }
  }
}
```

---

#### GET `/conversations/{id}`

Get single conversation detail.

**Headers:** `Authorization: Bearer <token>`

**Response (200):**

```json
{
  "success": true,
  "data": {
    "conversation": {
      "id": 123,
      "customer_number": "628123456789",
      "customer_name": "Customer Name",
      "customer_display": "+62 812-3456-789",
      "status": "open",
      "unread_count": 0,
      "waba_pool": {
        "id": 1,
        "account_name": "Business Account",
        "phone_number": "628111222333"
      },
      "agent": {
        "id": 5,
        "name": "Agent Name"
      },
      "is_assigned": true,
      "is_mine": true,
      "service_window": {
        "is_open": true,
        "expires_at": "2025-12-25T22:30:00Z",
        "remaining_minutes": 720,
        "requires_template": false
      },
      "created_at": "2025-12-20T08:00:00Z",
      "last_message_at": "2025-12-25T10:30:00Z"
    }
  }
}
```

---

### Message Endpoints

#### GET `/conversations/{id}/messages`

List messages in a conversation.

**Headers:** `Authorization: Bearer <token>`

**Query Parameters:**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `cursor` | string | null | Pagination cursor |
| `limit` | integer | 30 | Items per page (max 100) |
| `direction` | string | "before" | Pagination direction: `before`, `after` |

**Response (200):**

```json
{
  "success": true,
  "data": {
    "messages": [
      {
        "id": "msg_abc123",
        "wa_message_id": "wamid.xxx",
        "type": "text",
        "body": "Hello, how can I help you?",
        "direction": "outgoing",
        "status": "delivered",
        "timestamp": "2025-12-25T10:30:00Z",
        "sent_by": "agent",
        "media_url": null,
        "media_filename": null,
        "media_mime_type": null
      },
      {
        "id": "msg_def456",
        "wa_message_id": "wamid.yyy",
        "type": "image",
        "body": "Image caption here",
        "direction": "incoming",
        "status": null,
        "timestamp": "2025-12-25T10:28:00Z",
        "media_url": "https://nas.example.com/media/image.jpg",
        "media_filename": "image_20251225_102800.jpg",
        "media_mime_type": "image/jpeg"
      }
    ],
    "meta": {
      "has_more": true,
      "next_cursor": "eyJpZCI6NTB9",
      "prev_cursor": null
    }
  }
}
```

#### Message Types

| Type | Body | Media |
|------|------|-------|
| `text` | Text content | No |
| `image` | Caption (optional) | Yes |
| `video` | Caption (optional) | Yes |
| `audio` | "[Audio]" | Yes |
| `document` | Caption or filename | Yes |
| `sticker` | "[Sticker]" | Yes |
| `template` | "[Template: name]" | Depends |
| `location` | Location description | No |
| `contacts` | Contact info | No |

---

#### POST `/conversations/{id}/send`

Send a message to a conversation.

**Headers:** `Authorization: Bearer <token>`

**Request Parameters:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `type` | string | Yes | Message type: `text`, `image`, `document`, `video`, `audio` |
| `content` | string | Required for `text` | Text message content (max 4096 chars) |
| `media_url` | string | Required for media | URL of uploaded media file |
| `media_id` | string | No | WhatsApp media ID (alternative to media_url) |
| `caption` | string | No | Caption for media messages (max 1024 chars) |
| `filename` | string | No | Filename for document messages |

**Request (Text):**

```json
{
  "type": "text",
  "content": "Hello, this is a text message"
}
```

**Request (Image with caption):**

```json
{
  "type": "image",
  "media_url": "https://nas.example.com/media/image.jpg",
  "caption": "Image caption here"
}
```

**Request (Document):**

```json
{
  "type": "document",
  "media_url": "https://nas.example.com/media/document.pdf",
  "filename": "invoice.pdf",
  "caption": "Here is your invoice"
}
```

**Request (Audio):**

```json
{
  "type": "audio",
  "media_url": "https://nas.example.com/media/audio.mp3"
}
```

**Response (200 OK):**

```json
{
  "success": true,
  "message": "Message sent successfully.",
  "data": {
    "message_id": "wamid.HBgLNjI4MTIzNDU2Nzg...",
    "status": "sent",
    "type": "text"
  }
}
```

**Errors:**

| Code | Status | Description |
|------|--------|-------------|
| 400 | `WHATSAPP_API_ERROR` | Error from WhatsApp API |
| 422 | `VALIDATION_FAILED` | Missing or invalid required fields |
| 422 | `SERVICE_WINDOW_EXPIRED` | 24h service window expired, must use template |
| 404 | `CONVERSATION_NOT_FOUND` | Conversation not found or access denied |
| 500 | `WABA_NOT_FOUND` | WABA configuration not found |
| 500 | `WABA_TOKEN_MISSING` | WABA access token not configured |

---

#### POST `/conversations/{id}/send-template`

Send a template message.

**Headers:** `Authorization: Bearer <token>`

**Request:**

```json
{
  "template_name": "order_confirmation",
  "language": "id",
  "components": [
    {
      "type": "header",
      "parameters": [
        {
          "type": "image",
          "image": {
            "link": "https://example.com/header.jpg"
          }
        }
      ]
    },
    {
      "type": "body",
      "parameters": [
        { "type": "text", "text": "John Doe" },
        { "type": "text", "text": "ORD-12345" },
        { "type": "text", "text": "Rp 250.000" }
      ]
    }
  ]
}
```

**Response (200):**

```json
{
  "success": true,
  "message": "Template message sent successfully",
  "data": {
    "message_id": "wamid.xxx...",
    "status": "sent",
    "cost": "0.0350"
  }
}
```

---

#### POST `/conversations/{id}/mark-read`

Mark messages as read.

**Headers:** `Authorization: Bearer <token>`

**Request:**

```json
{
  "message_ids": ["msg_abc123", "msg_def456"]  // optional, marks all if empty
}
```

**Response (200):**

```json
{
  "success": true,
  "message": "Messages marked as read",
  "data": {
    "count": 5
  }
}
```

---

### Media Endpoints

#### POST `/media/upload`

Upload media file for sending.

**Headers:** `Authorization: Bearer <token>`

**Request:** `multipart/form-data`

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `file` | file | Yes | Media file |
| `type` | string | No | Force type: `image`, `video`, `audio`, `document` |

**Response (200):**

```json
{
  "success": true,
  "message": "File uploaded successfully",
  "data": {
    "url": "https://nas.example.com/media/image_20251225.jpg",
    "filename": "image_20251225_103500_a1b2c3d4.jpg",
    "mime_type": "image/jpeg",
    "size": 245890,
    "media_type": "image"
  }
}
```

**Errors:**

| Code | Status | Description |
|------|--------|-------------|
| 422 | `VALIDATION_FAILED` | File too large or invalid type |
| 500 | `UPLOAD_FAILED` | Upload to storage failed |

---

#### GET `/media/limits`

Get media upload limits.

**Headers:** `Authorization: Bearer <token>`

**Response (200):**

```json
{
  "success": true,
  "data": {
    "limits": {
      "image": {
        "max_size_bytes": 5242880,
        "max_size_display": "5 MB",
        "allowed_types": ["image/jpeg", "image/png", "image/webp"]
      },
      "video": {
        "max_size_bytes": 16777216,
        "max_size_display": "16 MB",
        "allowed_types": ["video/mp4", "video/3gpp"]
      },
      "audio": {
        "max_size_bytes": 16777216,
        "max_size_display": "16 MB",
        "allowed_types": ["audio/aac", "audio/mp4", "audio/mpeg", "audio/amr", "audio/ogg"]
      },
      "document": {
        "max_size_bytes": 104857600,
        "max_size_display": "100 MB",
        "allowed_types": ["application/pdf", "application/msword", "..."]
      },
      "sticker": {
        "max_size_bytes": 512000,
        "max_size_display": "500 KB",
        "allowed_types": ["image/webp"]
      }
    }
  }
}
```

---

### Template Endpoints

#### GET `/templates`

List available message templates.

**Headers:** `Authorization: Bearer <token>`

**Query Parameters:**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `search` | string | null | Search by name |
| `category` | string | null | Filter: `marketing`, `utility`, `authentication` |
| `status` | string | "APPROVED" | Filter by status |
| `limit` | integer | 20 | Items per page |
| `cursor` | string | null | Pagination cursor |

**Response (200):**

```json
{
  "success": true,
  "data": {
    "templates": [
      {
        "id": 1,
        "name": "order_confirmation",
        "language": "id",
        "category": "utility",
        "status": "APPROVED",
        "components": [
          {
            "type": "HEADER",
            "format": "IMAGE"
          },
          {
            "type": "BODY",
            "text": "Halo {{1}}, pesanan {{2}} senilai {{3}} telah dikonfirmasi."
          },
          {
            "type": "FOOTER",
            "text": "Terima kasih telah berbelanja"
          }
        ],
        "example_values": {
          "header_image": "https://example.com/sample.jpg",
          "body": ["John Doe", "ORD-12345", "Rp 250.000"]
        }
      }
    ],
    "meta": {
      "has_more": false,
      "next_cursor": null,
      "total_count": 15
    }
  }
}
```

---

### Agent Management Endpoints

#### POST `/conversations/{id}/claim`

**(Agent Only)** Claim an unassigned conversation.

**Headers:** `Authorization: Bearer <token>`

**Response (200):**

```json
{
  "success": true,
  "message": "Conversation claimed successfully",
  "data": {
    "conversation_id": 123,
    "agent_id": 5
  }
}
```

**Errors:**

| Code | Status | Description |
|------|--------|-------------|
| 400 | `ALREADY_ASSIGNED` | Conversation already has an agent |
| 403 | `NOT_ALLOWED` | Agent cannot claim chats |

---

#### POST `/conversations/{id}/release`

**(Agent Only)** Release assigned conversation.

**Headers:** `Authorization: Bearer <token>`

**Response (200):**

```json
{
  "success": true,
  "message": "Conversation released successfully"
}
```

---

#### POST `/conversations/{id}/close`

Close a conversation.

**Headers:** `Authorization: Bearer <token>`

**Response (200):**

```json
{
  "success": true,
  "message": "Conversation closed successfully"
}
```

---

#### POST `/conversations/{id}/reopen`

Reopen a closed conversation.

**Headers:** `Authorization: Bearer <token>`

**Response (200):**

```json
{
  "success": true,
  "message": "Conversation reopened successfully"
}
```

---

### Dashboard Endpoints

#### GET `/dashboard`

Get dashboard statistics.

**Headers:** `Authorization: Bearer <token>`

**Response (200):**

```json
{
  "success": true,
  "data": {
    "conversations": {
      "total": 150,
      "open": 45,
      "closed": 105,
      "unread": 12
    },
    "messages": {
      "sent_today": 230,
      "received_today": 185
    },
    "agent": {
      "assigned_to_me": 8,
      "resolved_today": 5
    },
    "balance": "150000.00",
    "waba_pools": [
      {
        "id": 1,
        "name": "Business Account",
        "conversations_count": 100
      }
    ]
  }
}
```

---

## Real-time Events (Pusher)

### Channels

| Channel | Format | Description |
|---------|--------|-------------|
| Private user | `private-user.{user_id}` | User-specific notifications |
| Private conversation | `private-conversation.{id}` | Real-time messages in conversation |
| Private agent | `private-agent.{agent_id}` | Agent-specific events |

### Events

#### `new-message`

Received on `private-conversation.{id}`:

```json
{
  "message": {
    "id": "msg_abc123",
    "type": "text",
    "body": "New message content",
    "direction": "incoming",
    "timestamp": "2025-12-25T10:30:00Z"
  }
}
```

#### `message-status`

Received on `private-conversation.{id}`:

```json
{
  "message_id": "wamid.xxx",
  "status": "delivered",
  "timestamp": "2025-12-25T10:30:05Z"
}
```

#### `new-conversation`

Received on `private-user.{user_id}`:

```json
{
  "conversation": {
    "id": 124,
    "customer_number": "628999888777",
    "customer_name": "New Customer"
  }
}
```

---

## SDKs & Code Examples

### Android (Kotlin)

```kotlin
// Login
val response = api.login(
    LoginRequest(
        email = "user@example.com",
        password = "password",
        deviceId = Settings.Secure.ANDROID_ID,
        deviceType = "android",
        fcmToken = FirebaseMessaging.getInstance().token.await()
    )
)

// Store token
PreferenceManager.setToken(response.data.token)

// Send message
val result = api.sendMessage(
    conversationId = 123,
    request = SendMessageRequest(
        type = "text",
        message = "Hello!"
    )
)
```

### iOS (Swift)

```swift
// Login
let response = try await api.login(
    email: "user@example.com",
    password: "password",
    deviceId: UIDevice.current.identifierForVendor?.uuidString ?? "",
    deviceType: "ios"
)

// Store token
KeychainWrapper.standard.set(response.data.token, forKey: "authToken")

// Send message
let result = try await api.sendMessage(
    conversationId: 123,
    type: .text,
    message: "Hello!"
)
```

---

## Changelog

### v1.0.0 (2025-12-25)

- Initial release
- Authentication with Laravel Sanctum
- Conversations list and detail
- Send text, image, video, audio, document messages
- Send template messages
- Media upload to NAS storage
- Real-time events via Pusher
- Agent claim/release functionality
