# X24 LABS - ADMIN API

**Version:** 1.0.0  
**Base URL:** https://api.x24labs.io

Centralized administration API for all X24 Labs applications.

## Table of Contents

- [Authentication](#authentication)
- [Auth](#auth)
- [Users](#users)
- [Apps](#apps)
- [API Keys](#api-keys)
- [Tickets](#tickets)
- [Health](#health)
- [Magic Link](#magic-link)
- [Wants](#wants)
- [Feedback](#feedback)
- [Joinlist](#joinlist)
- [Email Tracking](#email-tracking)
- [Webhooks](#webhooks)
- [Contacts](#contacts)
- [Slack Routing](#slack-routing)
- [Templates](#templates)
- [Email Jobs](#email-jobs)
- [Email Events](#email-events)
- [Email Analytics](#email-analytics)
- [Newsletter](#newsletter)

---

## Authentication

### JWT Bearer Token

```
Authorization: Bearer <token>
```

### API Key (`apiKeyAuth`)

Used for backend-to-backend authentication. Key in header.

```
X-API-Key: <key>
```

---

## Auth

Back Office authentication

### `POST` /auth/login

**Back Office team login**

Authentication for internal team users using email and password

**Auth:** None required

**Request Body:**

```json
{
  "email": string — e.g. "admin@bannersallover.com" (required)
  "password": string — e.g. "password123" (required)
}
```

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "token": string
  "user": {
    "id": string
    "email": string
    "name": string
    "role": string — e.g. "admin"
  }
}
```

**Response 400:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `POST` /auth/refresh

**Refresh JWT token**

Generate a new JWT token from an existing one

**Auth:** Bearer Token (JWT)

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "token": string
  "user": {}
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `POST` /auth/hash-password

**[DEV] Generate password hash**

Development endpoint to generate bcrypt hashes for passwords

**Auth:** None required

**Request Body:**

```json
{
  "password": string — e.g. "mypassword123" (required)
}
```

**Response 200:**

```json
{
  "hash": string
}
```

### `GET` /auth/me

**Current user information**

Get authenticated user information

**Auth:** Bearer Token (JWT)

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "user": {
    "id": string
    "email": string
    "name": string
    "role": string
  }
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

---

## Users

Admin user management

### `GET` /users/

**List users**

Get list of admin users

**Auth:** Bearer Token (JWT)

**Query Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `role` | string | No | Filter by role |
| `isActive` | string | No | Filter by active status |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": [{
    "_id": string
    "email": string
    "firstName": string
    "lastName": string
    "role": string
    "isActive": boolean
    "profile": {
      "avatar": string
      "bio": string
      "phone": string
      "timezone": string
      "language": string
    }
    "settings": {
      "notifications": {
        "email": boolean
        "push": boolean
      }
      "theme": string
      "defaultApp": string
    }
    "lastLoginAt": string
    "loginCount": number
    "createdAt": string
    "updatedAt": string
  }]
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `POST` /users/

**Create user**

Create new admin user

**Auth:** Bearer Token (JWT)

**Request Body:**

```json
{
  "email": string (required)
  "password": string (required)
  "firstName": string (required)
  "lastName": string (required)
  "role": string
  "profile": {
    "avatar": string
    "bio": string
    "phone": string
    "timezone": string
    "language": string
  }
  "settings": {
    "notifications": {
      "email": boolean
      "push": boolean
    }
    "theme": string
    "defaultApp": string
  }
}
```

**Response 201:**

```json
{
  "success": boolean — e.g. true
  "data": {}
  "message": string
}
```

**Response 400:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 409:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `GET` /users/{id}

**Get user by ID**

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `id` | string | User ID |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": {}
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `PATCH` /users/{id}

**Update user**

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `id` | string | - |

**Request Body:**

```json
{
  "firstName": string
  "lastName": string
  "role": string
  "isActive": boolean
  "profile": {
    "avatar": string
    "bio": string
    "phone": string
    "timezone": string
    "language": string
  }
  "settings": {
    "notifications": {
      "email": boolean
      "push": boolean
    }
    "theme": string
    "defaultApp": string
  }
}
```

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": {}
  "message": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `DELETE` /users/{id}

**Delete user**

Soft delete user (sets isActive to false)

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `id` | string | - |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "message": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `PATCH` /users/{id}/password

**Update user password**

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `id` | string | - |

**Request Body:**

```json
{
  "password": string (required)
}
```

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "message": string
}
```

**Response 400:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

---

## Apps

Application management

### `GET` /apps

**List of available applications**

**Auth:** Bearer Token (JWT)

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": [{
    "id": string — e.g. "app1"
    "name": string — e.g. "App 1"
    "features": {
      "canEditTemplates": boolean
      "canEditUsers": boolean
      "canViewMetrics": boolean
    }
  }]
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `GET` /admin/apps/

**List apps (admin)**

**Auth:** Bearer Token (JWT)

**Query Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `activeOnly` | string | No | Filter to active only |

**Response 200:**

```json
{
  "success": boolean
  "data": [{
    "_id": string
    "id": string
    "name": string
    "description": string
    "features": {}
    "slackChannels": {}
    "allowedOrigins": string[]
    "publicKey": string
    "active": boolean
    "createdAt": string
    "updatedAt": string
  }]
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 500:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `POST` /admin/apps/

**Create app**

**Auth:** Bearer Token (JWT)

**Request Body:**

```json
{
  "id": string (required)
  "name": string (required)
  "description": string
  "features": {}
  "slackChannels": {}
  "allowedOrigins": string[]
  "active": boolean
}
```

**Response 201:**

```json
{
  "success": boolean
  "data": {
    "_id": string
    "id": string
    "name": string
    "description": string
    "features": {}
    "slackChannels": {}
    "allowedOrigins": string[]
    "publicKey": string
    "active": boolean
    "createdAt": string
    "updatedAt": string
  }
}
```

**Response 400:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 409:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `GET` /admin/apps/{id}

**Get app by id**

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `id` | string | - |

**Response 200:**

```json
{
  "success": boolean
  "data": {
    "_id": string
    "id": string
    "name": string
    "description": string
    "features": {}
    "slackChannels": {}
    "allowedOrigins": string[]
    "publicKey": string
    "active": boolean
    "createdAt": string
    "updatedAt": string
  }
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `PATCH` /admin/apps/{id}

**Update app**

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `id` | string | - |

**Request Body:**

```json
{
  "name": string
  "description": string
  "features": {}
  "slackChannels": {}
  "allowedOrigins": string[]
  "active": boolean
}
```

**Response 200:**

```json
{
  "success": boolean
  "data": {
    "_id": string
    "id": string
    "name": string
    "description": string
    "features": {}
    "slackChannels": {}
    "allowedOrigins": string[]
    "publicKey": string
    "active": boolean
    "createdAt": string
    "updatedAt": string
  }
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `DELETE` /admin/apps/{id}

**Delete app**

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `id` | string | - |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "message": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `POST` /admin/apps/{id}/rotate-key

**Rotate app publicKey**

Generates a new pk_ key. Old key is invalidated immediately.

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `id` | string | - |

**Response 200:**

```json
{
  "success": boolean
  "data": {
    "id": string
    "publicKey": string
  }
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `POST` /admin/apps/{id}/toggle

**Toggle app active flag**

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `id` | string | - |

**Response 200:**

```json
{
  "success": boolean
  "data": {
    "_id": string
    "id": string
    "name": string
    "description": string
    "features": {}
    "slackChannels": {}
    "allowedOrigins": string[]
    "publicKey": string
    "active": boolean
    "createdAt": string
    "updatedAt": string
  }
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

---

## API Keys

API key management for backend-to-backend authentication

### `GET` /api-keys/stats

**API key statistics**

Get API key statistics (total, active, inactive, by app)

**Auth:** Bearer Token (JWT)

**Query Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `appId` | string | No | Filter by app ID |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": {
    "total": integer
    "active": integer
    "inactive": integer
    "expired": integer
    "byApp": {}
  }
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `GET` /api-keys/

**List API keys**

Get list of API keys with pagination and filters

**Auth:** Bearer Token (JWT)

**Query Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `page` | integer | No | Page number |
| `limit` | integer | No | Items per page |
| `appId` | string | No | Filter by app ID |
| `isActive` | string | No | Filter by active status |
| `search` | string | No | Search in name and description |
| `sortBy` | string | No | Sort by field |
| `sortOrder` | string | No | Sort order |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": [{
    "_id": string
    "appId": string
    "name": string
    "description": string
    "allowedOrigins": string[]
    "isActive": boolean
    "lastUsed": string
    "expiresAt": string
    "createdBy": string
    "createdAt": string
    "updatedAt": string
  }]
  "pagination": {
    "page": integer
    "limit": integer
    "total": integer
    "pages": integer
  }
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `POST` /api-keys/

**Create API key**

Create new API key for an app. The full key is returned only once.

**Auth:** Bearer Token (JWT)

**Request Body:**

```json
{
  "appId": string (required)
  "name": string (required)
  "description": string
  "allowedOrigins": string[]
  "expiresAt": string
}
```

**Response 201:**

```json
{
  "success": boolean — e.g. true
  "data": {
    "_id": string
    "key": string
    "appId": string
    "name": string
    "description": string
    "allowedOrigins": string[]
    "isActive": boolean
    "expiresAt": string
    "createdBy": string
    "createdAt": string
  }
  "message": string
}
```

**Response 400:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 500:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `GET` /api-keys/{keyId}

**Get API key**

Get API key details by ID (includes the full key value)

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `keyId` | string | API key ID |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": {
    "_id": string
    "key": string
    "appId": string
    "name": string
    "description": string
    "allowedOrigins": string[]
    "isActive": boolean
    "lastUsed": string
    "expiresAt": string
    "createdBy": string
    "createdAt": string
    "updatedAt": string
  }
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `PATCH` /api-keys/{keyId}

**Update API key**

Update API key details (name, description, expiration)

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `keyId` | string | API key ID |

**Request Body:**

```json
{
  "name": string
  "description": string
  "allowedOrigins": string[]
  "isActive": boolean
  "expiresAt": string
}
```

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": {}
  "message": string
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 500:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `DELETE` /api-keys/{keyId}

**Delete API key**

Permanently delete an API key

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `keyId` | string | API key ID |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "message": string
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 500:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `POST` /api-keys/{keyId}/revoke

**Revoke API key**

Revoke (deactivate) an API key

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `keyId` | string | API key ID |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": {}
  "message": string
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 500:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `POST` /api-keys/{keyId}/regenerate

**Regenerate API key**

Generate a new key value for an existing API key. The new key is returned only once.

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `keyId` | string | API key ID |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": {
    "_id": string
    "key": string
    "appId": string
    "name": string
    "isActive": boolean
    "createdAt": string
  }
  "message": string
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 500:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

---

## Tickets

Support tickets management

### `GET` /tickets/

**List all tickets**

Get paginated list of tickets from all apps (optionally filter by appId)

**Auth:** Bearer Token (JWT)

**Query Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `page` | integer | No | Page number |
| `limit` | integer | No | Items per page |
| `appId` | string | No | Optional: Filter by app ID |
| `status` | string | No | Filter by status |
| `topic` | string | No | Filter by topic |
| `priority` | string | No | Filter by priority |
| `shopId` | string | No | Filter by shop ID |
| `email` | string | No | Filter by email |
| `startDate` | string | No | Start date filter |
| `endDate` | string | No | End date filter |
| `search` | string | No | Search in subject, message, and email |
| `sortBy` | string | No | - |
| `sortOrder` | string | No | - |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": object[]
  "pagination": {
    "page": integer
    "limit": integer
    "total": integer
    "pages": integer
  }
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `GET` /tickets/{messageId}

**Get ticket by ID**

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `messageId` | string | Ticket ID |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": {}
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `PATCH` /tickets/{messageId}

**Update ticket**

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `messageId` | string | - |

**Request Body:**

```json
{
  "status": string
  "priority": string
}
```

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": {}
  "message": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `DELETE` /tickets/{messageId}

**Delete ticket**

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `messageId` | string | - |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "message": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `PATCH` /tickets/{messageId}/status

**Update ticket status**

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `messageId` | string | - |

**Request Body:**

```json
{
  "status": string (required)
}
```

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": {}
  "message": string
}
```

**Response 400:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `GET` /{appId}/tickets/stats

**Ticket statistics**

Get aggregated ticket statistics for an app

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |

**Query Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `startDate` | string | No | Start date filter |
| `endDate` | string | No | End date filter |
| `shopId` | string | No | Filter by shop ID |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": {
    "total": integer
    "byStatus": {}
    "byTopic": {}
    "byPriority": {}
    "recentActivity": [{
      "_id": string
      "subject": string
      "email": string
      "status": string
      "priority": string
      "createdAt": string
    }]
    "volumeOverTime": [{
      "date": string
      "count": integer
    }]
    "averageResolutionTime": number
  }
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `POST` /{appId}/tickets/

**Create ticket with attachments**

Create new support ticket with optional file attachments.

**Content Types:**
- `application/json`: For tickets without attachments
- `multipart/form-data`: For tickets with file attachments

**File Constraints:**
- Max file size: 50MB per file
- Max files: 5 per ticket
- Allowed types: images (jpeg, png, gif, webp, svg), videos (mp4, webm, quicktime), PDF

**Multipart Fields:**
- `shopId`: string (required)
- `topic`: enum (required)
- `email`: string (required)
- `subject`: string (required)
- `message`: string (required)
- `priority`: enum (optional, default: normal)
- `attachments`: file(s) (optional, can send multiple)

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |

**Response 201:**

```json
{
  "success": boolean — e.g. true
  "data": {
    "_id": string
    "appId": string
    "shopId": string
    "topic": string
    "email": string
    "subject": string
    "message": string
    "status": string
    "priority": string
    "attachments": [{
      "url": string
      "key": string
      "filename": string
      "mimetype": string
      "size": number
      "uploadedAt": string
    }]
    "createdAt": string
    "updatedAt": string
  }
  "message": string
}
```

**Response 400:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 500:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `GET` /{appId}/tickets/

**List tickets**

Get paginated list of tickets for an app

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |

**Query Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `page` | integer | No | Page number |
| `limit` | integer | No | Items per page |
| `status` | string | No | Filter by status |
| `topic` | string | No | Filter by topic |
| `priority` | string | No | Filter by priority |
| `shopId` | string | No | Filter by shop ID |
| `email` | string | No | Filter by email |
| `startDate` | string | No | Start date filter |
| `endDate` | string | No | End date filter |
| `search` | string | No | Search in subject, message, and email |
| `sortBy` | string | No | - |
| `sortOrder` | string | No | - |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": object[]
  "pagination": {
    "page": integer
    "limit": integer
    "total": integer
    "pages": integer
  }
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `DELETE` /{appId}/tickets/

**Bulk delete tickets**

Delete multiple tickets by IDs

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |

**Request Body:**

```json
{
  "ids": string[] (required)
}
```

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "message": string
  "deletedCount": integer
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `GET` /{appId}/tickets/{messageId}

**Get ticket by ID**

Get a single ticket by its ID

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |
| `messageId` | string | Ticket ID |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": {}
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `PATCH` /{appId}/tickets/{messageId}

**Update ticket**

Update ticket fields (status, priority, etc.)

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |
| `messageId` | string | Ticket ID |

**Request Body:**

```json
{
  "status": string
  "priority": string
  "topic": string
}
```

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": {}
  "message": string
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `DELETE` /{appId}/tickets/{messageId}

**Delete ticket**

Delete a ticket by ID

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |
| `messageId` | string | Ticket ID |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "message": string
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `PATCH` /{appId}/tickets/{messageId}/status

**Update ticket status**

Update only the status of a ticket

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |
| `messageId` | string | Ticket ID |

**Request Body:**

```json
{
  "status": string (required)
}
```

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": {}
  "message": string
}
```

**Response 400:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `POST` /{appId}/tickets/{messageId}/messages

**Add message to ticket**

Add a reply/message to an existing ticket

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |
| `messageId` | string | Ticket ID |

**Request Body:**

```json
{
  "content": string (required)
  "isCustomer": boolean
  "authorEmail": string
  "authorName": string
}
```

**Response 201:**

```json
{
  "success": boolean — e.g. true
  "data": {}
  "message": string
}
```

**Response 400:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `GET` /{appId}/tickets/{messageId}/messages

**Get ticket messages**

Get all messages/replies for a ticket

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |
| `messageId` | string | Ticket ID |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": [{
    "_id": string
    "content": string
    "isCustomer": boolean
    "authorEmail": string
    "authorName": string
    "createdAt": string
  }]
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

---

## Health

Health checks

### `GET` /health

**Server health check**

**Auth:** None required

**Response 200:**

```json
{
  "status": string — e.g. "ok"
  "timestamp": string
  "uptime": number
  "memory": {
    "rss": string
    "heapUsed": string
    "heapTotal": string
  }
  "database": {
    "connected": boolean
    "modelsCount": integer
  }
}
```

---

## Magic Link

Passwordless end-user authentication

### `POST` /auth/magic-link/

**Send magic link**

**Auth:** None required

**Request Body:**

```json
{
  "email": string (required)
  "appId": string — e.g. "app1" (required)
}
```

**Response 200:**

```json
{
  "success": boolean
  "message": string
}
```

**Response 400:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 429:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `POST` /auth/magic-link/verify

**Verify magic link token**

**Auth:** None required

**Request Body:**

```json
{
  "token": string (required)
}
```

**Response 200:**

```json
{
  "success": boolean
  "token": string
  "user": {
    "id": string
    "email": string
    "appId": string
  }
}
```

**Response 400:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

---

## Wants

Feature request board with voting and roadmap

### `GET` /{appId}/wants/

**List wants**

Get paginated list of wants for an app. If authenticated, includes hasVoted status.

**Auth:** API Key (header: X-API-Key)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |

**Query Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `page` | integer | No | Page number |
| `limit` | integer | No | Items per page |
| `status` | string | No | Filter by status |
| `category` | string | No | Filter by category |
| `sort` | string | No | Sort order |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": [{
    "_id": string
    "appId": string
    "title": string
    "description": string
    "category": string
    "status": string
    "voteCount": integer
    "hasVoted": boolean
    "authorEmail": string
    "createdAt": string
    "updatedAt": string
  }]
  "pagination": {
    "page": integer
    "limit": integer
    "total": integer
    "pages": integer
  }
}
```

**Response 400:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 500:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `POST` /{appId}/wants/

**Submit a want**

Submit a new want. Requires end-user authentication. Rate limited to 10 per hour.

**Auth:** API Key (header: X-API-Key)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |

**Request Body:**

```json
{
  "title": string (required)
  "description": string (required)
  "category": string (required)
}
```

**Response 201:**

```json
{
  "success": boolean — e.g. true
  "data": {
    "_id": string
    "appId": string
    "title": string
    "description": string
    "category": string
    "status": string
    "voteCount": integer
    "hasVoted": boolean
    "authorEmail": string
    "createdAt": string
    "updatedAt": string
  }
}
```

**Response 400:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 429:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 500:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `GET` /{appId}/wants/{id}

**Get want by ID**

Get a single want. If authenticated, includes hasVoted status.

**Auth:** API Key (header: X-API-Key)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |
| `id` | string | Feature request ID |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": {
    "_id": string
    "appId": string
    "title": string
    "description": string
    "category": string
    "status": string
    "voteCount": integer
    "hasVoted": boolean
    "authorEmail": string
    "createdAt": string
    "updatedAt": string
  }
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 500:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `POST` /{appId}/wants/{id}/vote

**Vote on a want**

Toggle vote on a want. Requires end-user authentication. Rate limited to 30 per hour.

**Auth:** API Key (header: X-API-Key)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |
| `id` | string | Feature request ID |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": {
    "voteCount": integer
    "hasVoted": boolean
  }
}
```

**Response 400:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 429:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 500:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `GET` /admin/{appId}/wants/stats

**Feature request statistics**

Get aggregated want statistics for an app

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": {
    "total": integer
    "byStatus": {}
    "byCategory": {}
    "byPriority": {}
  }
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `GET` /admin/{appId}/wants/

**List wants (admin)**

Get paginated list of wants with full admin data

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |

**Query Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `page` | integer | No | Page number |
| `limit` | integer | No | Items per page |
| `status` | string | No | Filter by status |
| `category` | string | No | Filter by category |
| `sort` | string | No | Sort order |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": [{
    "_id": string
    "appId": string
    "title": string
    "description": string
    "category": string
    "status": string
    "priority": string
    "voteCount": integer
    "authorEmail": string
    "createdAt": string
    "updatedAt": string
  }]
  "pagination": {
    "page": integer
    "limit": integer
    "total": integer
    "pages": integer
  }
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `GET` /admin/{appId}/wants/{id}

**Get want by ID (admin)**

Get a single want with full admin data

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |
| `id` | string | Feature request ID |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": {
    "_id": string
    "appId": string
    "title": string
    "description": string
    "category": string
    "status": string
    "priority": string
    "voteCount": integer
    "authorEmail": string
    "createdAt": string
    "updatedAt": string
  }
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `PATCH` /admin/{appId}/wants/{id}

**Update want**

Update want fields (title, description, category)

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |
| `id` | string | Feature request ID |

**Request Body:**

```json
{
  "title": string
  "description": string
  "category": string
}
```

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": {
    "_id": string
    "appId": string
    "title": string
    "description": string
    "category": string
    "status": string
    "priority": string
    "voteCount": integer
    "authorEmail": string
    "createdAt": string
    "updatedAt": string
  }
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 500:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `DELETE` /admin/{appId}/wants/{id}

**Delete want**

Permanently delete a want

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |
| `id` | string | Feature request ID |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "message": string
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 500:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `PATCH` /admin/{appId}/wants/{id}/status

**Update want status**

Update only the status of a want

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |
| `id` | string | Feature request ID |

**Request Body:**

```json
{
  "status": string (required)
}
```

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": {
    "_id": string
    "appId": string
    "title": string
    "description": string
    "category": string
    "status": string
    "priority": string
    "voteCount": integer
    "authorEmail": string
    "createdAt": string
    "updatedAt": string
  }
}
```

**Response 400:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `PATCH` /admin/{appId}/wants/{id}/priority

**Update want priority**

Update only the priority of a want

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |
| `id` | string | Feature request ID |

**Request Body:**

```json
{
  "priority": string (required)
}
```

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": {
    "_id": string
    "appId": string
    "title": string
    "description": string
    "category": string
    "status": string
    "priority": string
    "voteCount": integer
    "authorEmail": string
    "createdAt": string
    "updatedAt": string
  }
}
```

**Response 400:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

---

## Feedback

Quick anonymous feedback submission

### `POST` /{appId}/feedback/

**Submit feedback**

Submit anonymous feedback for an app. Rate limited to 10 submissions per hour per IP.

**Auth:** None required

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |

**Request Body:**

```json
{
  "type": string (required)
  "subject": string (required)
  "message": string (required)
  "email": string
}
```

**Response 201:**

```json
{
  "success": boolean — e.g. true
  "data": {
    "_id": string
    "appId": string
    "type": string
    "subject": string
    "message": string
    "email": string
    "status": string
    "priority": string
    "createdAt": string
    "updatedAt": string
  }
}
```

**Response 400:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 429:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 500:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `GET` /admin/{appId}/feedback/stats

**Feedback statistics**

Get aggregated feedback statistics for an app

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": {
    "total": integer
    "byType": {}
    "byStatus": {}
  }
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `GET` /admin/{appId}/feedback/

**List feedback**

Get paginated list of feedback for an app with filters

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |

**Query Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `page` | integer | No | Page number |
| `limit` | integer | No | Items per page |
| `type` | string | No | Filter by type |
| `status` | string | No | Filter by status |
| `priority` | string | No | Filter by priority |
| `sortBy` | string | No | - |
| `sortOrder` | string | No | - |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": object[]
  "pagination": {
    "page": integer
    "limit": integer
    "total": integer
    "pages": integer
  }
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `GET` /admin/{appId}/feedback/{id}

**Get feedback by ID**

Get a single feedback entry by its ID

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |
| `id` | string | Feedback ID |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": {}
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `PATCH` /admin/{appId}/feedback/{id}

**Update feedback**

Update feedback fields (type, subject, message)

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |
| `id` | string | Feedback ID |

**Request Body:**

```json
{
  "type": string
  "subject": string
  "message": string
}
```

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": {}
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `DELETE` /admin/{appId}/feedback/{id}

**Delete feedback**

Delete a feedback entry by ID

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |
| `id` | string | Feedback ID |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "message": string
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `PATCH` /admin/{appId}/feedback/{id}/status

**Update feedback status**

Update only the status of a feedback entry

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |
| `id` | string | Feedback ID |

**Request Body:**

```json
{
  "status": string (required)
}
```

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": {}
}
```

**Response 400:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `PATCH` /admin/{appId}/feedback/{id}/priority

**Update feedback priority**

Update only the priority of a feedback entry

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |
| `id` | string | Feedback ID |

**Request Body:**

```json
{
  "priority": string (required)
}
```

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": {}
}
```

**Response 400:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

---

## Joinlist

### `POST` /joinlist

**Public newsletter signup**

Public capture endpoint for joinlist subscriptions. Requires `X-App-Key` (publishable pk_) and a matching `Origin` registered in the app's allowedOrigins. Routes to contacts + slack notification + welcome email. Rate-limited to 10 requests per minute per IP.

**Auth:** None required

**Request Body:**

```json
{
  "appId": string
  "email": string (required)
  "firstName": string
  "locale": string
  "source": string
  "referralUrl": string
  "website": string
}
```

**Response 200:**

```json
{
  "success": boolean
  "alreadySubscribed": boolean
  "unsubscribeUrl": string
}
```

**Response 400:**

```json
{
  "success": boolean
  "error": string
}
```

**Response 403:**

```json
{
  "success": boolean
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean
  "error": string
}
```

**Response 429:**

```json
{
  "success": boolean
  "error": string
}
```

**Response 500:**

```json
{
  "success": boolean
  "error": string
}
```

### `GET` /unsubscribe/{token}

**Public unsubscribe confirmation**

One-click unsubscribe via token. Always returns 200 HTML (no enumeration).

**Auth:** None required

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `token` | string | - |

**Response 200:**

```json
string
```

---

## Email Tracking

### `GET` /o/{token}

**Open tracking pixel**

Returns a 1x1 transparent GIF and records an "opened" event. Always returns 200.

**Auth:** None required

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `token` | string | Signed tracking token (may end with .gif) |

**Response 200:**

```json
string
```

### `GET` /c/{token}

**Click tracking redirect**

Records a "clicked" event and 302-redirects to the original URL.

**Auth:** None required

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `token` | string | Signed tracking token |

**Response 302:**

```json
string
```

---

## Webhooks

### `POST` /email-events/resend

**Resend email webhook receiver**

Receives email lifecycle events from Resend (sent, delivered, bounced, etc.). Verified by HMAC signature.

**Auth:** None required

**Request Body:**

```json
{}
```

**Response 200:**

```json
{
  "success": boolean
  "processed": boolean
  "eventId": string
}
```

### `POST` /email-events/cold-provider

**Cold email provider webhook (stub)**

Placeholder endpoint for future cold-email provider integration.

**Auth:** None required

**Request Body:**

```json
{}
```

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "message": string
}
```

---

## Contacts

### `GET` /admin/contacts/

**List contacts**

Get paginated list of contacts with optional filters by email, consent category, or tag

**Auth:** Bearer Token (JWT)

**Query Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `page` | integer | No | Page number |
| `limit` | integer | No | Items per page |
| `search` | string | No | Search by email, first name, or last name |
| `category` | string | No | Filter by consent category |
| `categoryStatus` | string | No | Filter by consent status (requires category) |
| `tag` | string | No | Filter by tag |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": object[]
  "pagination": {
    "page": integer
    "limit": integer
    "total": integer
    "pages": integer
  }
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `GET` /admin/contacts/{id}

**Get contact by ID**

Get contact details including full consent change history

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `id` | string | Contact ID |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": {}
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `DELETE` /admin/contacts/{id}

**GDPR delete contact**

Soft-delete and anonymize a contact (right-to-be-forgotten). PII is removed but consent audit log is retained anonymized.

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `id` | string | Contact ID |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "message": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `PATCH` /admin/contacts/{id}/consent

**Update consent for a category**

Update consent status for a specific category. Creates an immutable consent_event log entry.

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `id` | string | Contact ID |

**Request Body:**

```json
{
  "category": string (required)
  "status": boolean (required)
  "reason": string
}
```

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": {}
  "message": string
}
```

**Response 400:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `POST` /admin/contacts/{id}/opt-out

**Global opt-out**

Opt out contact from all email categories. No emails will be sent to this contact.

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `id` | string | Contact ID |

**Request Body:**

```json
{
  "reason": string
}
```

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": {}
  "message": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `GET` /admin/contacts/{id}/export

**GDPR data export**

Export all data for a contact (right-to-access). Returns contact details and full consent history.

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `id` | string | Contact ID |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": {}
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

---

## Slack Routing

### `GET` /admin/slack-routing/

**List routing rules**

List all per-app Slack webhook routing rules, optionally filtered by appId

**Auth:** Bearer Token (JWT)

**Query Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `appId` | string | No | Filter by app ID |

**Response 200:**

```json
{
  "success": boolean
  "data": [{
    "_id": string
    "appId": string
    "channel": string
    "webhookUrl": string
    "active": boolean
    "createdAt": string
  }]
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `POST` /admin/slack-routing/

**Create routing rule**

Create a per-app webhook override for a Slack notification channel

**Auth:** Bearer Token (JWT)

**Request Body:**

```json
{
  "appId": string (required)
  "channel": string (required)
  "webhookUrl": string (required)
}
```

**Response 201:**

```json
{
  "success": boolean
  "data": {}
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 409:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `PATCH` /admin/slack-routing/{id}

**Update routing rule**

Update webhook URL or active status for a routing rule

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `id` | string | - |

**Request Body:**

```json
{
  "webhookUrl": string
  "active": boolean
}
```

**Response 200:**

```json
{
  "success": boolean
  "data": {}
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `DELETE` /admin/slack-routing/{id}

**Delete routing rule**

Remove a per-app Slack routing rule (falls back to global env var)

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `id` | string | - |

**Response 200:**

```json
{
  "success": boolean
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

---

## Templates

### `GET` /admin/templates/categories

**Template languages**

Get all available template languages with counts

**Auth:** Bearer Token (JWT)

**Query Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `appId` | string | No | Filter by appId (omit for global) |

### `GET` /admin/templates/

**List templates**

Get paginated list of email templates

**Auth:** Bearer Token (JWT)

**Query Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `page` | integer | No | Page number |
| `limit` | integer | No | Items per page |
| `appId` | string | No | Filter by appId (omit for global) |
| `language` | string | No | Filter by language |
| `isActive` | boolean | No | Filter by active status |
| `search` | string | No | Search by name or subject |

### `POST` /admin/templates/

**Create template**

Create a new email template

**Auth:** Bearer Token (JWT)

**Request Body:**

```json
{
  "appId": string,null
  "name": string (required)
  "language": string (required)
  "subject": string (required)
  "htmlTemplate": string (required)
  "textTemplate": string (required)
  "variables": string[]
  "isActive": boolean
  "version": number
  "sender": {
    "name": string
    "email": string
  }
}
```

### `GET` /admin/templates/{templateId}

**Get template by ID**

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `templateId` | string | - |

**Query Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `appId` | string | No | - |

### `PUT` /admin/templates/{templateId}

**Update template (full)**

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `templateId` | string | - |

### `PATCH` /admin/templates/{templateId}

**Update template (partial)**

Partial update - only updates provided fields

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `templateId` | string | - |

### `DELETE` /admin/templates/{templateId}

**Delete template**

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `templateId` | string | - |

**Query Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `appId` | string | No | - |

### `PATCH` /admin/templates/{templateId}/toggle

**Toggle template active status**

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `templateId` | string | - |

**Request Body:**

```json
{
  "appId": string,null
  "isActive": boolean (required)
}
```

### `POST` /admin/templates/{templateId}/duplicate

**Duplicate template**

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `templateId` | string | - |

**Request Body:**

```json
{
  "appId": string,null
  "name": string
}
```

---

## Email Jobs

### `GET` /admin/email-jobs/

**List email jobs**

List email jobs with filtering and pagination

**Auth:** Bearer Token (JWT)

**Query Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `page` | integer | No | Page number |
| `limit` | integer | No | Items per page |
| `status` | string | No | Filter by job status |
| `type` | string | No | Filter by job type |
| `campaignId` | string | No | Filter by campaign ID |

**Response 200:**

```json
{
  "success": boolean
  "data": [{
    "_id": string
    "type": string
    "status": string
    "priority": integer
    "payload": {}
    "result": {}
    "attempts": integer
    "maxAttempts": integer
    "lastError": string
    "scheduledAt": string
    "startedAt": string
    "completedAt": string
    "createdAt": string
  }]
  "pagination": {
    "page": integer
    "limit": integer
    "total": integer
    "pages": integer
  }
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `GET` /admin/email-jobs/stats

**Email job statistics**

Get aggregate job queue statistics, optionally filtered by campaign

**Auth:** Bearer Token (JWT)

**Query Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `campaignId` | string | No | Filter stats by campaign ID |

**Response 200:**

```json
{
  "success": boolean
  "data": {
    "pending": integer
    "processing": integer
    "completed": integer
    "failed": integer
    "dead": integer
    "total": integer
    "progress": {
      "total": integer
      "completed": integer
      "failed": integer
      "pending": integer
      "processing": integer
      "dead": integer
      "percentage": integer
    }
  }
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `GET` /admin/email-jobs/{id}

**Get email job details**

Get a single email job by ID with full payload, result, and error details

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `id` | string | Job ID |

**Response 200:**

```json
{
  "success": boolean
  "data": {
    "_id": string
    "type": string
    "status": string
    "priority": integer
    "payload": {}
    "result": {}
    "attempts": integer
    "maxAttempts": integer
    "lastError": string
    "scheduledAt": string
    "startedAt": string
    "completedAt": string
    "createdAt": string
  }
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

---

## Email Events

### `GET` /admin/email-events/

**List email events**

List email lifecycle events with filters and pagination.

**Auth:** Bearer Token (JWT)

**Query Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `page` | integer | No | Page number |
| `limit` | integer | No | Items per page |
| `contactId` | string | No | Filter by contact ID |
| `campaignId` | string | No | Filter by campaign ID |
| `messageId` | string | No | Filter by message ID |
| `type` | string | No | Filter by event type |
| `provider` | string | No | Filter by provider |
| `appId` | string | No | Filter by app ID |

**Response 200:**

```json
{
  "success": boolean
  "data": [{
    "_id": string
    "contactId": string
    "messageId": string
    "campaignId": string
    "provider": string
    "type": string
    "createdAt": string
  }]
  "pagination": {
    "page": integer
    "limit": integer
    "total": integer
    "pages": integer
  }
}
```

**Response 500:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `GET` /admin/email-events/message/{messageId}

**Get events for a message**

Get all email lifecycle events for a specific message ID.

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `messageId` | string | Resend message ID |

**Response 200:**

```json
{
  "success": boolean
  "data": [{
    "_id": string
    "type": string
    "provider": string
    "createdAt": string
  }]
}
```

**Response 500:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `GET` /admin/email-events/campaign/{campaignId}/stats

**Campaign event statistics**

Aggregated event counts and rates for a specific campaign.

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `campaignId` | string | Campaign ID |

**Response 200:**

```json
{
  "success": boolean
  "data": {
    "sent": integer
    "delivered": integer
    "opened": integer
    "clicked": integer
    "bounced": integer
    "openRate": number
    "clickRate": number
  }
}
```

**Response 500:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

---

## Email Analytics

### `GET` /admin/email-events/analytics/campaigns/{campaignId}

**Campaign analytics**

Full campaign performance: sent, delivered, opened, clicked, bounced, unique counts, open rate (unique opens / delivered), CTR (unique clicks / unique opens), top clicked links, and event timeline.

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `campaignId` | string | Campaign ID |

**Response 200:**

```json
{
  "success": boolean
  "data": {
    "campaignId": string
    "sent": integer
    "delivered": integer
    "opened": integer
    "uniqueOpens": integer
    "clicked": integer
    "uniqueClicks": integer
    "bounced": integer
    "complained": integer
    "unsubscribed": integer
    "openRate": number
    "clickThroughRate": number
    "topLinks": [{
      "url": string
      "clicks": integer
    }]
    "timeline": [{
      "date": string
      "type": string
      "count": integer
    }]
  }
}
```

**Response 500:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `GET` /admin/email-events/analytics/campaigns/{campaignId}/recipients

**Campaign recipients breakdown**

Per-recipient status breakdown for a campaign. Shows each contact and which event types they triggered.

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `campaignId` | string | Campaign ID |

**Query Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `page` | integer | No | Page number |
| `limit` | integer | No | Items per page |

**Response 200:**

```json
{
  "success": boolean
  "data": [{
    "contactId": string
    "events": string[]
    "lastEvent": string
  }]
  "pagination": {
    "page": integer
    "limit": integer
    "total": integer
    "pages": integer
  }
}
```

**Response 500:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `GET` /admin/email-events/analytics/contacts/{contactId}

**Contact engagement history**

Full engagement history for a contact across all campaigns. Includes summary counts and paginated event timeline.

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `contactId` | string | Contact ID |

**Query Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `page` | integer | No | Page number |
| `limit` | integer | No | Items per page |

**Response 200:**

```json
{
  "success": boolean
  "data": {
    "contactId": string
    "totalSent": integer
    "totalOpened": integer
    "totalClicked": integer
    "totalBounced": integer
    "totalComplained": integer
    "events": [{
      "_id": string
      "campaignId": string
      "messageId": string
      "type": string
      "createdAt": string
    }]
  }
}
```

**Response 500:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `GET` /admin/email-events/analytics/overview

**Global email analytics overview**

Dashboard KPIs for a time range. Aggregate numbers across all campaigns: totals, rates, and daily timeline.

**Auth:** Bearer Token (JWT)

**Query Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `from` | string | No | Start of time range (ISO 8601, UTC) |
| `to` | string | No | End of time range (ISO 8601, UTC) |

**Response 200:**

```json
{
  "success": boolean
  "data": {
    "totalSent": integer
    "totalDelivered": integer
    "totalOpened": integer
    "totalUniqueOpens": integer
    "totalClicked": integer
    "totalUniqueClicks": integer
    "totalBounced": integer
    "totalComplained": integer
    "totalUnsubscribed": integer
    "openRate": number
    "clickThroughRate": number
    "bounceRate": number
    "complaintRate": number
    "timeline": [{
      "date": string
      "sent": integer
      "delivered": integer
      "opened": integer
      "clicked": integer
    }]
  }
}
```

**Response 500:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

---

## Newsletter

### `POST` /{appId}/newsletter/subscribe

**Subscribe to newsletter**

Subscribe an email to the newsletter for an app. Requires API Key. Rate limited.

**Auth:** API Key (header: X-API-Key)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |

**Request Body:**

```json
{
  "email": string (required)
  "firstName": string
  "locale": string — e.g. "en"
  "referralUrl": string
}
```

**Response 201:**

```json
{
  "success": boolean — e.g. true
  "data": {
    "email": string
    "firstName": string
  }
}
```

**Response 400:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 429:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 500:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `GET` /{appId}/newsletter/unsubscribe/{token}

**Unsubscribe from newsletter**

One-click unsubscribe via token. Requires API Key. Returns an HTML confirmation page.

**Auth:** API Key (header: X-API-Key)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |
| `token` | string | Unsubscribe token |

**Response 200:**

```json
string
```

### `POST` /{appId}/newsletter/unsubscribe/{token}

**Unsubscribe from newsletter (POST)**

Unsubscribe via POST request for programmatic use. Requires API Key.

**Auth:** API Key (header: X-API-Key)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |
| `token` | string | Unsubscribe token |

**Response 200:**

```json
string
```

### `GET` /admin/{appId}/newsletter/subscribers/stats

**Subscriber statistics**

Get aggregated subscriber statistics for an app

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": {
    "total": integer
    "active": integer
    "unsubscribed": integer
    "bounced": integer
    "bySource": {}
  }
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `GET` /admin/{appId}/newsletter/subscribers

**List subscribers**

Get paginated list of newsletter subscribers

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |

**Query Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `page` | integer | No | Page number |
| `limit` | integer | No | Items per page |
| `status` | string | No | Filter by status |
| `search` | string | No | Search by email or name |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": [{
    "_id": string
    "email": string
    "firstName": string
    "lastName": string
    "locale": string
    "subscriptions": [{
      "appId": string
      "status": string
      "source": string
      "referralUrl": string
      "subscribedAt": string
      "unsubscribedAt": string
    }]
    "createdAt": string
  }]
  "pagination": {
    "page": integer
    "limit": integer
    "total": integer
    "pages": integer
  }
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `POST` /admin/{appId}/newsletter/subscribers

**Add subscriber manually**

Manually add a subscriber to the newsletter

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |

**Request Body:**

```json
{
  "email": string (required)
  "firstName": string
  "locale": string
}
```

**Response 201:**

```json
{
  "success": boolean
  "data": {
    "_id": string
    "email": string
    "firstName": string
    "lastName": string
    "locale": string
    "subscriptions": [{
      "appId": string
      "status": string
      "source": string
      "referralUrl": string
      "subscribedAt": string
      "unsubscribedAt": string
    }]
    "createdAt": string
  }
}
```

**Response 400:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `POST` /admin/{appId}/newsletter/subscribers/import

**Import subscribers**

Bulk import subscribers from a JSON array

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |

**Request Body:**

```json
{
  "subscribers": [{
    "email": string (required)
    "firstName": string
    "locale": string
  }] (required)
}
```

**Response 200:**

```json
{
  "success": boolean
  "data": {
    "imported": integer
    "skipped": integer
  }
}
```

**Response 400:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `DELETE` /admin/{appId}/newsletter/subscribers/{id}

**Delete subscriber**

Permanently remove a subscriber

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |
| `id` | string | Subscriber ID |

**Response 200:**

```json
{
  "success": boolean
  "message": string
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `GET` /admin/{appId}/newsletter/campaigns

**List campaigns**

Get paginated list of newsletter campaigns

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |

**Query Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `page` | integer | No | Page number |
| `limit` | integer | No | Items per page |
| `status` | string | No | Filter by status |

**Response 200:**

```json
{
  "success": boolean — e.g. true
  "data": [{
    "_id": string
    "appId": string
    "name": string
    "subject": string
    "status": string
    "stats": {
      "total": integer
      "sent": integer
      "failed": integer
    }
    "sentAt": string
    "completedAt": string
    "createdBy": string
    "createdAt": string
    "updatedAt": string
  }]
  "pagination": {
    "page": integer
    "limit": integer
    "total": integer
    "pages": integer
  }
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `POST` /admin/{appId}/newsletter/campaigns

**Create campaign**

Create a new newsletter campaign (draft)

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |

**Request Body:**

```json
{
  "name": string (required)
  "subject": string (required)
  "htmlContent": string (required)
  "textContent": string
  "templateId": string
}
```

**Response 201:**

```json
{
  "success": boolean
  "data": {
    "_id": string
    "appId": string
    "name": string
    "subject": string
    "status": string
    "stats": {
      "total": integer
      "sent": integer
      "failed": integer
    }
    "sentAt": string
    "completedAt": string
    "createdBy": string
    "createdAt": string
    "updatedAt": string
  }
}
```

**Response 400:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `GET` /admin/{appId}/newsletter/campaigns/{id}

**Get campaign**

Get campaign details including content and stats

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |
| `id` | string | Campaign ID |

**Response 200:**

```json
{
  "success": boolean
  "data": {
    "_id": string
    "appId": string
    "name": string
    "subject": string
    "status": string
    "stats": {
      "total": integer
      "sent": integer
      "failed": integer
    }
    "sentAt": string
    "completedAt": string
    "createdBy": string
    "createdAt": string
    "updatedAt": string
  }
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `PATCH` /admin/{appId}/newsletter/campaigns/{id}

**Update campaign**

Update campaign fields (only draft campaigns can be updated)

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |
| `id` | string | Campaign ID |

**Request Body:**

```json
{
  "name": string
  "subject": string
  "htmlContent": string
  "textContent": string
}
```

**Response 200:**

```json
{
  "success": boolean
  "data": {
    "_id": string
    "appId": string
    "name": string
    "subject": string
    "status": string
    "stats": {
      "total": integer
      "sent": integer
      "failed": integer
    }
    "sentAt": string
    "completedAt": string
    "createdBy": string
    "createdAt": string
    "updatedAt": string
  }
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `DELETE` /admin/{appId}/newsletter/campaigns/{id}

**Delete campaign**

Delete a campaign (only draft campaigns can be deleted)

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |
| `id` | string | Campaign ID |

**Response 200:**

```json
{
  "success": boolean
  "message": string
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `POST` /admin/{appId}/newsletter/campaigns/{id}/send

**Send campaign**

Send the campaign to all active subscribers. Enqueues a batch-plan job that fans out individual sends.

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |
| `id` | string | Campaign ID |

**Response 200:**

```json
{
  "success": boolean
  "data": {
    "status": string — e.g. "queued"
    "jobId": string
  }
}
```

**Response 400:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 409:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `POST` /admin/{appId}/newsletter/campaigns/{id}/test

**Send test email**

Send a test email to a single address to preview the campaign

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |
| `id` | string | Campaign ID |

**Request Body:**

```json
{
  "email": string (required)
}
```

**Response 200:**

```json
{
  "success": boolean
  "message": string
  "data": {
    "jobId": string
  }
}
```

**Response 400:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `GET` /admin/{appId}/newsletter/templates

**List newsletter templates**

Get all newsletter templates

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |

**Response 200:**

```json
{
  "success": boolean
  "data": [{
    "_id": string
    "name": string
    "subject": string
    "htmlContent": string
    "textContent": string
    "isActive": boolean
    "createdBy": string
    "createdAt": string
    "updatedAt": string
  }]
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `POST` /admin/{appId}/newsletter/templates

**Create newsletter template**

Create a reusable newsletter template

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |

**Request Body:**

```json
{
  "name": string (required)
  "subject": string (required)
  "htmlContent": string (required)
  "textContent": string
}
```

**Response 201:**

```json
{
  "success": boolean
  "data": {
    "_id": string
    "name": string
    "subject": string
    "htmlContent": string
    "textContent": string
    "isActive": boolean
    "createdBy": string
    "createdAt": string
    "updatedAt": string
  }
}
```

**Response 400:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `PATCH` /admin/{appId}/newsletter/templates/{id}

**Update newsletter template**

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |
| `id` | string | Template ID |

**Request Body:**

```json
{
  "name": string
  "subject": string
  "htmlContent": string
  "textContent": string
  "isActive": boolean
}
```

**Response 200:**

```json
{
  "success": boolean
  "data": {
    "_id": string
    "name": string
    "subject": string
    "htmlContent": string
    "textContent": string
    "isActive": boolean
    "createdBy": string
    "createdAt": string
    "updatedAt": string
  }
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

### `DELETE` /admin/{appId}/newsletter/templates/{id}

**Delete newsletter template**

**Auth:** Bearer Token (JWT)

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `appId` | string | Application ID (e.g.: banners-all-over) |
| `id` | string | Template ID |

**Response 200:**

```json
{
  "success": boolean
  "message": string
}
```

**Response 401:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```

**Response 404:**

```json
{
  "success": boolean — e.g. false
  "error": string
}
```
