# Merchandillo External API

Version: `1.0.0`
OpenAPI: `3.0.3`

The Merchandillo External API allows you to synchronize orders from your e-commerce store to the Merchandillo platform and manage newsletter operations for that store. ## Supported Store Types - **OpenCart**: Fully supported with dedicated endpoints at `/api/opencart/orders/*` - **WooCommerce**: Fully supported with dedicated endpoints at `/api/woocommerce/orders/*` - **Custom Integration**: Fully supported with dedicated endpoints at `/api/custom/orders/*` - **Newsletter Management**: Store-scoped public endpoints at `/api/public/v1/newsletters/*` Each store type has its own endpoints to accommodate different tracking requirements and special rules. ## Authentication All endpoints require API Key and API Secret authentication. You can obtain these credentials from your store settings in the Merchandillo dashboard. **Authentication Methods:** 1. Authorization header: `Authorization: Bearer API_KEY:API_SECRET` (Recommended) 2. Custom headers: `X-API-Key` and `X-API-Secret` 3. Query parameters: `api_key` and `api_secret` (Testing only) ## Rate Limits - **Window:** 60 seconds - **Free plan:** 200 single operations, 20 batch operations - **Plus plan:** 600 single operations, 60 batch operations - **Pro plan:** 1200 single operations, 120 batch operations - **Custom integration endpoints (`/api/custom/orders/*`) use the OpenCart limits** When rate limit is exceeded, you'll receive a 429 status code with a `Retry-After` header indicating how many seconds to wait before retrying. ## Status Values Order and payment statuses are stored verbatim as free-form strings (max 255 characters). You can send any platform-specific value. For consistency, we recommend the following defaults: `pending`, `processing`, `completed`, `cancelled`, `refunded`, `failed` for order status and `pending`, `paid`, `failed`, `refunded` for payment status.

# Servers

- `https://data.merchandillo.com` - Merchandillo API

# Security Schemes

- `bearerAuth`: type=`http`, scheme=`bearer`
  API Key and Secret in the format: `API_KEY:API_SECRET` Example: `Authorization: Bearer sk_abc123:ss_xyz789`
- `apiKeyHeader`: type=`apiKey`, name=`X-API-Key`, in=`header`
  API Key provided in custom header. Must be used together with X-API-Secret header. Example: ``` X-API-Key: sk_abc123 X-API-Secret: ss_xyz789 ```

# Endpoints

## GET /api/custom/orders
List Orders
Retrieve a list of orders for your store with optional filtering and pagination. **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120.
Operation ID: `listCustomOrders`
Tags: `Custom Orders`

### Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `status` | query | `string` | no | Filter by order status (free-form string, exact match). Recommended values: pending, processing, completed, cancelled, refunded, failed. |
| `date_from` | query | `string` | no | Filter orders from this date (YYYY-MM-DD) |
| `date_to` | query | `string` | no | Filter orders up to this date (YYYY-MM-DD) |
| `customer_search` | query | `string` | no | Search by customer name, email, or phone |
| `total_min` | query | `number` | no | Minimum total amount |
| `total_max` | query | `number` | no | Maximum total amount |
| `sort_by` | query | `string` | no | Field to sort by |
| `sort_direction` | query | `string` | no | Sort direction |
| `include_vouchers` | query | `boolean` | no | Include parsed voucher data in each order response item |
| `page` | query | `integer` | no | Page number (1-based) |
| `limit` | query | `integer` | no | Number of results per page |

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | List of orders retrieved successfully | `object` |
| 401 |  |  |
| 429 |  |  |
| 500 |  |  |

## POST /api/custom/orders
Create Single Order
Import a single order from Custom to Merchandillo. **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120. **Duplicate Handling:** If an order with the same `order_number` already exists for this store, the existing order will be updated instead of creating a new one.
Operation ID: `createCustomOrder`
Tags: `Custom Orders`

### Request Body
Required: yes
Schema: `#/components/schemas/OrderInput`
Example:
```json
{
  "order_number": "12345",
  "customer_name": "John Doe",
  "customer_email": "john.doe@example.com",
  "customer_phone": "+30 210 1234567",
  "total_amount": 150.5,
  "subtotal": 130,
  "tax_amount": 15.5,
  "shipping_amount": 5,
  "discount_amount": 0,
  "currency": "EUR",
  "status": "processing",
  "payment_method": "Credit Card",
  "payment_status": "paid",
  "shipping_method": "Standard Shipping",
  "tracking_number": "TRACK123456",
  "courier": "acs",
  "tracking_url": "https://tracking.example.com/TRACK123456",
  "voucher": {
    "courier": "acs",
    "tracking_number": "TRACK123456",
    "tracking_url": "https://tracking.example.com/TRACK123456",
    "voucher_number": "ACS-V-12345"
  },
  "order_date": "2025-11-13",
  "notes": "Customer requested gift wrapping",
  "shipping_address": {
    "first_name": "John",
    "last_name": "Doe",
    "address_1": "123 Main Street",
    "address_2": "Apt 4B",
    "city": "Athens",
    "postcode": "10431",
    "country": "Greece",
    "zone": "Attica"
  },
  "billing_address": {
    "first_name": "John",
    "last_name": "Doe",
    "address_1": "123 Main Street",
    "city": "Athens",
    "postcode": "10431",
    "country": "Greece"
  },
  "items": [
    {
      "product_sku": "PROD-001",
      "product_name": "Wireless Mouse",
      "product_id": "12345",
      "quantity": 2,
      "price": 25,
      "total_price": 50,
      "tax_amount": 5,
      "product_options": {
        "color": "Black"
      }
    }
  ]
}
```

### Responses
| Status | Description | Schema |
|---|---|---|
| 201 | Order created successfully | `ref:OrderResponse` |
| 400 |  |  |
| 401 |  |  |
| 429 |  |  |
| 500 |  |  |

## POST /api/custom/orders/batch
Create Batch Orders
Import multiple orders in a single request for efficient bulk synchronization. **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120. **Batch Limits:** - Minimum: 1 order - Maximum: 200 orders per request - Recommended: 50-100 orders for optimal performance **Duplicate Handling:** Orders with duplicate `order_number` values (within the batch or against existing orders) will be updated instead of creating duplicates.
Operation ID: `createCustomBatchOrders`
Tags: `Custom Orders`

### Request Body
Required: yes
Schema: `object`

### Responses
| Status | Description | Schema |
|---|---|---|
| 201 | All orders created successfully | `ref:BatchOrderResponse` |
| 207 | Partial success - some orders failed | `ref:BatchOrderResponse` |
| 400 |  |  |
| 401 |  |  |
| 429 |  |  |
| 500 |  |  |

## GET /api/custom/orders/{orderId}
Get Single Order
Retrieve a specific order by its UUID or `order_number`. **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120.
Operation ID: `getCustomOrder`
Tags: `Custom Orders`

### Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `orderId` | path | `string` | yes | Order UUID or `order_number` |

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Order retrieved successfully | `ref:OrderResponse` |
| 401 |  |  |
| 404 |  |  |
| 429 |  |  |
| 500 |  |  |

## PUT /api/custom/orders/{orderId}
Update Order
Update an existing order. All fields are optional. **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120.
Operation ID: `updateCustomOrder`
Tags: `Custom Orders`

### Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `orderId` | path | `string` | yes | Order UUID |

### Request Body
Required: yes
Schema: `#/components/schemas/OrderUpdateInput`
Example:
```json
{
  "status": "completed",
  "tracking_number": "TRACK789456",
  "courier": "acs",
  "tracking_url": "https://tracking.example.com/TRACK789456",
  "voucher": {
    "courier": "acs",
    "tracking_number": "TRACK789456",
    "tracking_url": "https://tracking.example.com/TRACK789456",
    "voucher_number": "ACS-V-789456"
  },
  "notes": "Order delivered successfully"
}
```

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Order updated successfully | `ref:OrderResponse` |
| 400 |  |  |
| 401 |  |  |
| 404 |  |  |
| 429 |  |  |
| 500 |  |  |

## DELETE /api/custom/orders/{orderId}
Delete Order
Delete an order from the system. **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120.
Operation ID: `deleteCustomOrder`
Tags: `Custom Orders`

### Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `orderId` | path | `string` | yes | Order UUID |

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Order deleted successfully | `object` |
| 401 |  |  |
| 404 |  |  |
| 429 |  |  |
| 500 |  |  |

## GET /api/opencart/orders
List Orders
Retrieve a list of orders for your store with optional filtering and pagination. **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120.
Operation ID: `listOrders`
Tags: `OpenCart Orders`

### Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `status` | query | `string` | no | Filter by order status (free-form string, exact match). Recommended values: pending, processing, completed, cancelled, refunded, failed. |
| `date_from` | query | `string` | no | Filter orders from this date (YYYY-MM-DD) |
| `date_to` | query | `string` | no | Filter orders up to this date (YYYY-MM-DD) |
| `customer_search` | query | `string` | no | Search by customer name, email, or phone |
| `total_min` | query | `number` | no | Minimum total amount |
| `total_max` | query | `number` | no | Maximum total amount |
| `sort_by` | query | `string` | no | Field to sort by |
| `sort_direction` | query | `string` | no | Sort direction |
| `include_vouchers` | query | `boolean` | no | Include parsed voucher data in each order response item |
| `page` | query | `integer` | no | Page number (1-based) |
| `limit` | query | `integer` | no | Number of results per page |

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | List of orders retrieved successfully | `object` |
| 401 |  |  |
| 429 |  |  |
| 500 |  |  |

## POST /api/opencart/orders
Create Single Order
Import a single order from OpenCart/WooCommerce to Merchandillo. **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120. **Duplicate Handling:** If an order with the same `order_number` already exists for this store, the existing order will be updated instead of creating a new one.
Operation ID: `createOrder`
Tags: `OpenCart Orders`

### Request Body
Required: yes
Schema: `#/components/schemas/OrderInput`
Example:
```json
{
  "order_number": "12345",
  "customer_name": "John Doe",
  "customer_email": "john.doe@example.com",
  "customer_phone": "+30 210 1234567",
  "total_amount": 150.5,
  "subtotal": 130,
  "tax_amount": 15.5,
  "shipping_amount": 5,
  "discount_amount": 0,
  "currency": "EUR",
  "status": "processing",
  "payment_method": "Credit Card",
  "payment_status": "paid",
  "shipping_method": "Standard Shipping",
  "tracking_number": "TRACK123456",
  "courier": "acs",
  "tracking_url": "https://tracking.example.com/TRACK123456",
  "voucher": {
    "courier": "acs",
    "tracking_number": "TRACK123456",
    "tracking_url": "https://tracking.example.com/TRACK123456",
    "voucher_number": "ACS-V-12345"
  },
  "order_date": "2025-11-13",
  "notes": "Customer requested gift wrapping",
  "shipping_address": {
    "first_name": "John",
    "last_name": "Doe",
    "address_1": "123 Main Street",
    "address_2": "Apt 4B",
    "city": "Athens",
    "postcode": "10431",
    "country": "Greece",
    "zone": "Attica"
  },
  "billing_address": {
    "first_name": "John",
    "last_name": "Doe",
    "address_1": "123 Main Street",
    "city": "Athens",
    "postcode": "10431",
    "country": "Greece"
  },
  "items": [
    {
      "product_sku": "PROD-001",
      "product_name": "Wireless Mouse",
      "product_id": "12345",
      "quantity": 2,
      "price": 25,
      "total_price": 50,
      "tax_amount": 5,
      "product_options": {
        "color": "Black"
      }
    }
  ]
}
```

### Responses
| Status | Description | Schema |
|---|---|---|
| 201 | Order created successfully | `ref:OrderResponse` |
| 400 |  |  |
| 401 |  |  |
| 429 |  |  |
| 500 |  |  |

## POST /api/opencart/orders/batch
Create Batch Orders
Import multiple orders in a single request for efficient bulk synchronization. **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120. **Batch Limits:** - Minimum: 1 order - Maximum: 200 orders per request - Recommended: 50-100 orders for optimal performance **Duplicate Handling:** Orders with duplicate `order_number` values (within the batch or against existing orders) will be updated instead of creating duplicates.
Operation ID: `createBatchOrders`
Tags: `OpenCart Orders`

### Request Body
Required: yes
Schema: `object`

### Responses
| Status | Description | Schema |
|---|---|---|
| 201 | All orders created successfully | `ref:BatchOrderResponse` |
| 207 | Partial success - some orders failed | `ref:BatchOrderResponse` |
| 400 |  |  |
| 401 |  |  |
| 429 |  |  |
| 500 |  |  |

## GET /api/opencart/orders/{orderId}
Get Single Order
Retrieve a specific order by its UUID or `order_number`. **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120.
Operation ID: `getOrder`
Tags: `OpenCart Orders`

### Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `orderId` | path | `string` | yes | Order UUID or `order_number` |

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Order retrieved successfully | `ref:OrderResponse` |
| 401 |  |  |
| 404 |  |  |
| 429 |  |  |
| 500 |  |  |

## PUT /api/opencart/orders/{orderId}
Update Order
Update an existing order. All fields are optional. **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120.
Operation ID: `updateOrder`
Tags: `OpenCart Orders`

### Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `orderId` | path | `string` | yes | Order UUID |

### Request Body
Required: yes
Schema: `#/components/schemas/OrderUpdateInput`
Example:
```json
{
  "status": "completed",
  "tracking_number": "TRACK789456",
  "courier": "acs",
  "tracking_url": "https://tracking.example.com/TRACK789456",
  "voucher": {
    "courier": "acs",
    "tracking_number": "TRACK789456",
    "tracking_url": "https://tracking.example.com/TRACK789456",
    "voucher_number": "ACS-V-789456"
  },
  "notes": "Order delivered successfully"
}
```

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Order updated successfully | `ref:OrderResponse` |
| 400 |  |  |
| 401 |  |  |
| 404 |  |  |
| 429 |  |  |
| 500 |  |  |

## DELETE /api/opencart/orders/{orderId}
Delete Order
Delete an order from the system. **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120.
Operation ID: `deleteOrder`
Tags: `OpenCart Orders`

### Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `orderId` | path | `string` | yes | Order UUID |

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Order deleted successfully | `object` |
| 401 |  |  |
| 404 |  |  |
| 429 |  |  |
| 500 |  |  |

## GET /api/public/v1/newsletters/analytics/overview
Get Newsletter Analytics Overview
Operation ID: `getNewsletterAnalyticsOverview`
Tags: `Newsletters`

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Newsletter analytics overview retrieved successfully | `ref:NewsletterFreeformResponse` |
| 401 |  |  |
| 429 |  |  |
| 500 |  |  |

## GET /api/public/v1/newsletters/analytics/{campaignUuid}
Get Newsletter Campaign Analytics
Operation ID: `getNewsletterCampaignAnalytics`
Tags: `Newsletters`

### Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `campaignUuid` | path | `string` | yes |  |

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Campaign analytics retrieved successfully | `ref:NewsletterFreeformResponse` |
| 401 |  |  |
| 404 |  |  |
| 429 |  |  |
| 500 |  |  |

## GET /api/public/v1/newsletters/analytics/{campaignUuid}/clicks
List Newsletter Click Events
Operation ID: `listNewsletterClickEvents`
Tags: `Newsletters`

### Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `campaignUuid` | path | `string` | yes |  |

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Click events retrieved successfully | `ref:NewsletterFreeformResponse` |
| 401 |  |  |
| 404 |  |  |
| 429 |  |  |
| 500 |  |  |

## GET /api/public/v1/newsletters/analytics/{campaignUuid}/opens
List Newsletter Open Events
Operation ID: `listNewsletterOpenEvents`
Tags: `Newsletters`

### Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `campaignUuid` | path | `string` | yes |  |

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Open events retrieved successfully | `ref:NewsletterFreeformResponse` |
| 401 |  |  |
| 404 |  |  |
| 429 |  |  |
| 500 |  |  |

## GET /api/public/v1/newsletters/analytics/{campaignUuid}/recipients
List Newsletter Campaign Recipients
Operation ID: `listNewsletterCampaignRecipients`
Tags: `Newsletters`

### Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `campaignUuid` | path | `string` | yes |  |
| `type` | query | `string` | no |  |
| `page` | query | `integer` | no |  |
| `limit` | query | `integer` | no |  |
| `q` | query | `string` | no |  |

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Campaign recipients retrieved successfully | `ref:NewsletterFreeformResponse` |
| 401 |  |  |
| 404 |  |  |
| 429 |  |  |
| 500 |  |  |

## GET /api/public/v1/newsletters/campaigns
List Newsletter Campaigns
Operation ID: `listNewsletterCampaigns`
Tags: `Newsletters`

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Newsletter campaigns retrieved successfully | `ref:NewsletterCampaignListResponse` |
| 401 |  |  |
| 429 |  |  |
| 500 |  |  |

## POST /api/public/v1/newsletters/campaigns
Create Newsletter Campaign
Operation ID: `createNewsletterCampaign`
Tags: `Newsletters`

### Request Body
Required: yes
Schema: `#/components/schemas/NewsletterCampaignCreateInput`

### Responses
| Status | Description | Schema |
|---|---|---|
| 201 | Newsletter campaign created successfully | `ref:NewsletterCampaignMutationResponse` |
| 400 |  |  |
| 401 |  |  |
| 429 |  |  |
| 500 |  |  |

## GET /api/public/v1/newsletters/campaigns/{campaignUuid}
Get Newsletter Campaign
Operation ID: `getNewsletterCampaign`
Tags: `Newsletters`

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Newsletter campaign retrieved successfully | `ref:NewsletterCampaignResponse` |
| 401 |  |  |
| 404 |  |  |
| 429 |  |  |
| 500 |  |  |

## PUT /api/public/v1/newsletters/campaigns/{campaignUuid}
Update Newsletter Campaign
Operation ID: `updateNewsletterCampaign`
Tags: `Newsletters`

### Request Body
Required: yes
Schema: `#/components/schemas/NewsletterCampaignUpdateInput`

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Newsletter campaign updated successfully | `ref:NewsletterSimpleSuccessResponse` |
| 400 |  |  |
| 401 |  |  |
| 404 |  |  |
| 429 |  |  |
| 500 |  |  |

## DELETE /api/public/v1/newsletters/campaigns/{campaignUuid}
Delete Newsletter Campaign
Operation ID: `deleteNewsletterCampaign`
Tags: `Newsletters`

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Newsletter campaign deleted successfully | `ref:NewsletterSimpleSuccessResponse` |
| 401 |  |  |
| 404 |  |  |
| 429 |  |  |
| 500 |  |  |

## POST /api/public/v1/newsletters/campaigns/{campaignUuid}/cancel
Cancel Newsletter Campaign
Operation ID: `cancelNewsletterCampaign`
Tags: `Newsletters`

### Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `campaignUuid` | path | `string` | yes |  |

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Campaign cancelled successfully | `ref:NewsletterSimpleSuccessResponse` |
| 401 |  |  |
| 404 |  |  |
| 429 |  |  |
| 500 |  |  |

## POST /api/public/v1/newsletters/campaigns/{campaignUuid}/clone
Clone Newsletter Campaign
Operation ID: `cloneNewsletterCampaign`
Tags: `Newsletters`

### Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `campaignUuid` | path | `string` | yes |  |

### Responses
| Status | Description | Schema |
|---|---|---|
| 201 | Campaign cloned successfully | `ref:NewsletterCampaignMutationResponse` |
| 401 |  |  |
| 404 |  |  |
| 429 |  |  |
| 500 |  |  |

## POST /api/public/v1/newsletters/campaigns/{campaignUuid}/pause
Pause Newsletter Campaign
Operation ID: `pauseNewsletterCampaign`
Tags: `Newsletters`

### Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `campaignUuid` | path | `string` | yes |  |

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Campaign paused successfully | `ref:NewsletterSimpleSuccessResponse` |
| 401 |  |  |
| 404 |  |  |
| 429 |  |  |
| 500 |  |  |

## GET /api/public/v1/newsletters/campaigns/{campaignUuid}/runs
List Newsletter Campaign Runs
Operation ID: `listNewsletterCampaignRuns`
Tags: `Newsletters`

### Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `campaignUuid` | path | `string` | yes |  |
| `page` | query | `integer` | no |  |
| `limit` | query | `integer` | no |  |

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Campaign runs retrieved successfully | `ref:NewsletterCampaignRunsResponse` |
| 401 |  |  |
| 404 |  |  |
| 429 |  |  |
| 500 |  |  |

## GET /api/public/v1/newsletters/campaigns/{campaignUuid}/runs/{runUuid}
Get Newsletter Campaign Run
Operation ID: `getNewsletterCampaignRun`
Tags: `Newsletters`

### Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `campaignUuid` | path | `string` | yes |  |
| `runUuid` | path | `string` | yes |  |

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Campaign run retrieved successfully | `ref:NewsletterCampaignRunResponse` |
| 401 |  |  |
| 404 |  |  |
| 429 |  |  |
| 500 |  |  |

## POST /api/public/v1/newsletters/campaigns/{campaignUuid}/schedule
Schedule Newsletter Campaign
Operation ID: `scheduleNewsletterCampaign`
Tags: `Newsletters`

### Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `campaignUuid` | path | `string` | yes |  |

### Request Body
Required: yes
Schema: `#/components/schemas/NewsletterScheduleInput`

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Campaign scheduled successfully | `ref:NewsletterSimpleSuccessResponse` |
| 400 |  |  |
| 401 |  |  |
| 404 |  |  |
| 429 |  |  |
| 500 |  |  |

## POST /api/public/v1/newsletters/campaigns/{campaignUuid}/send
Start Newsletter Campaign Send
Operation ID: `sendNewsletterCampaign`
Tags: `Newsletters`

### Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `campaignUuid` | path | `string` | yes |  |

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Campaign send kickoff accepted (queueing starts asynchronously) | `ref:NewsletterActionResponse` |
| 400 |  |  |
| 401 |  |  |
| 404 |  |  |
| 409 | Campaign is already sending or currently queueing | `ref:Error` |
| 429 |  |  |
| 500 |  |  |

## POST /api/public/v1/newsletters/campaigns/{campaignUuid}/send-remaining
Send Remaining Newsletter Recipients
Operation ID: `sendRemainingNewsletterCampaignRecipients`
Tags: `Newsletters`

### Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `campaignUuid` | path | `string` | yes |  |

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Remaining eligible recipients queued successfully | `ref:NewsletterActionResponse` |
| 400 |  |  |
| 401 |  |  |
| 404 |  |  |
| 429 |  |  |
| 500 |  |  |

## GET /api/public/v1/newsletters/customers
Search Newsletter Customers
Operation ID: `searchNewsletterCustomers`
Tags: `Newsletters`

### Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `query` | query | `string` | no |  |
| `limit` | query | `integer` | no |  |

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Newsletter customers retrieved successfully | `ref:NewsletterCustomerSearchResponse` |
| 401 |  |  |
| 429 |  |  |
| 500 |  |  |

## GET /api/public/v1/newsletters/groups
List Customer Groups
Operation ID: `listNewsletterGroups`
Tags: `Newsletters`

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Customer groups retrieved successfully | `ref:NewsletterGroupListResponse` |
| 401 |  |  |
| 429 |  |  |
| 500 |  |  |

## POST /api/public/v1/newsletters/groups
Create Customer Group
Operation ID: `createNewsletterGroup`
Tags: `Newsletters`

### Request Body
Required: yes
Schema: `#/components/schemas/NewsletterGroupCreateInput`

### Responses
| Status | Description | Schema |
|---|---|---|
| 201 | Customer group created successfully | `ref:NewsletterGroupMutationResponse` |
| 400 |  |  |
| 401 |  |  |
| 429 |  |  |
| 500 |  |  |

## POST /api/public/v1/newsletters/groups/preview
Preview Customer Group Rules
Operation ID: `previewNewsletterGroupRules`
Tags: `Newsletters`

### Request Body
Required: yes
Schema: `object`

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Customer group preview retrieved successfully | `ref:NewsletterGroupPreviewResponse` |
| 400 |  |  |
| 401 |  |  |
| 429 |  |  |
| 500 |  |  |

## GET /api/public/v1/newsletters/groups/{groupUuid}
Get Customer Group
Operation ID: `getNewsletterGroup`
Tags: `Newsletters`

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Customer group retrieved successfully | `ref:NewsletterGroupResponse` |
| 401 |  |  |
| 404 |  |  |
| 429 |  |  |
| 500 |  |  |

## PUT /api/public/v1/newsletters/groups/{groupUuid}
Update Customer Group
Operation ID: `updateNewsletterGroup`
Tags: `Newsletters`

### Request Body
Required: yes
Schema: `#/components/schemas/NewsletterGroupUpdateInput`

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Customer group updated successfully | `ref:NewsletterSimpleSuccessResponse` |
| 400 |  |  |
| 401 |  |  |
| 404 |  |  |
| 429 |  |  |
| 500 |  |  |

## DELETE /api/public/v1/newsletters/groups/{groupUuid}
Delete Customer Group
Operation ID: `deleteNewsletterGroup`
Tags: `Newsletters`

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Customer group deleted successfully | `ref:NewsletterSimpleSuccessResponse` |
| 401 |  |  |
| 404 |  |  |
| 429 |  |  |
| 500 |  |  |

## GET /api/public/v1/newsletters/groups/{groupUuid}/customers
List Group Customers
Operation ID: `listNewsletterGroupCustomers`
Tags: `Newsletters`

### Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `groupUuid` | path | `string` | yes |  |
| `page` | query | `integer` | no |  |
| `limit` | query | `integer` | no |  |
| `query` | query | `string` | no | Search contacts by email or name. |
| `status` | query | `string` | no |  |

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Group customers retrieved successfully | `ref:NewsletterGroupCustomersResponse` |
| 401 |  |  |
| 404 |  |  |
| 429 |  |  |
| 500 |  |  |

## POST /api/public/v1/newsletters/groups/{groupUuid}/refresh
Refresh Customer Group Membership
Operation ID: `refreshNewsletterGroup`
Tags: `Newsletters`

### Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `groupUuid` | path | `string` | yes |  |

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Customer group refreshed successfully | `ref:NewsletterActionResponse` |
| 401 |  |  |
| 404 |  |  |
| 429 |  |  |
| 500 |  |  |

## GET /api/public/v1/newsletters/products
Search Newsletter Products
Operation ID: `searchNewsletterProducts`
Tags: `Newsletters`

### Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `query` | query | `string` | no | Search product name, SKU, or external product id. |
| `limit` | query | `integer` | no |  |

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Products retrieved from existing orders | `ref:NewsletterProductSearchResponse` |
| 401 |  |  |
| 429 |  |  |
| 500 |  |  |

## GET /api/public/v1/newsletters/send/limits
Get Newsletter Send Limits
Operation ID: `getNewsletterSendLimits`
Tags: `Newsletters`

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Newsletter send limits retrieved successfully | `ref:NewsletterFreeformResponse` |
| 401 |  |  |
| 429 |  |  |
| 500 |  |  |

## POST /api/public/v1/newsletters/send/preview
Render Newsletter Preview
Operation ID: `previewNewsletterSend`
Tags: `Newsletters`

### Request Body
Required: yes
Schema: `#/components/schemas/NewsletterSendPreviewInput`

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Newsletter preview rendered successfully | `ref:NewsletterFreeformResponse` |
| 400 |  |  |
| 401 |  |  |
| 429 |  |  |
| 500 |  |  |

## GET /api/public/v1/newsletters/send/queue/status
Get Newsletter Queue Status
Operation ID: `getNewsletterQueueStatus`
Tags: `Newsletters`

### Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `campaign_uuid` | query | `string` | yes |  |

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Queue status retrieved successfully | `ref:NewsletterFreeformResponse` |
| 400 |  |  |
| 401 |  |  |
| 429 |  |  |
| 500 |  |  |

## POST /api/public/v1/newsletters/send/retry
Retry Failed Newsletter Sends
Operation ID: `retryNewsletterSends`
Tags: `Newsletters`

### Request Body
Required: yes
Schema: `object`

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Failed sends re-queued successfully | `ref:NewsletterActionResponse` |
| 400 |  |  |
| 401 |  |  |
| 429 |  |  |
| 500 |  |  |

## POST /api/public/v1/newsletters/send/test
Send Newsletter Test Email
Operation ID: `sendNewsletterTest`
Tags: `Newsletters`

### Request Body
Required: yes
Schema: `#/components/schemas/NewsletterSendTestInput`

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Newsletter test send completed successfully | `ref:NewsletterFreeformResponse` |
| 400 |  |  |
| 401 |  |  |
| 429 |  |  |
| 500 |  |  |

## GET /api/public/v1/newsletters/templates
List Newsletter Templates
Operation ID: `listNewsletterTemplates`
Tags: `Newsletters`

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Newsletter templates retrieved successfully | `ref:NewsletterTemplateListResponse` |
| 401 |  |  |
| 429 |  |  |
| 500 |  |  |

## POST /api/public/v1/newsletters/templates
Create Newsletter Template
Operation ID: `createNewsletterTemplate`
Tags: `Newsletters`

### Request Body
Required: yes
Schema: `#/components/schemas/NewsletterTemplateInput`

### Responses
| Status | Description | Schema |
|---|---|---|
| 201 | Newsletter template created successfully | `ref:NewsletterTemplateCreateResponse` |
| 400 |  |  |
| 401 |  |  |
| 429 |  |  |
| 500 |  |  |

## PUT /api/public/v1/newsletters/templates/{templateUuid}
Update Newsletter Template
Operation ID: `updateNewsletterTemplate`
Tags: `Newsletters`

### Request Body
Required: yes
Schema: `#/components/schemas/NewsletterTemplateInput`

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Newsletter template updated successfully | `ref:NewsletterSimpleSuccessResponse` |
| 400 |  |  |
| 401 |  |  |
| 404 |  |  |
| 429 |  |  |
| 500 |  |  |

## DELETE /api/public/v1/newsletters/templates/{templateUuid}
Delete Newsletter Template
Operation ID: `deleteNewsletterTemplate`
Tags: `Newsletters`

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Newsletter template deleted successfully | `ref:NewsletterSimpleSuccessResponse` |
| 401 |  |  |
| 404 |  |  |
| 429 |  |  |
| 500 |  |  |

## GET /api/public/v1/newsletters/unsubscribes
List Newsletter Unsubscribes
Operation ID: `listNewsletterUnsubscribes`
Tags: `Newsletters`

### Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `page` | query | `integer` | no |  |
| `limit` | query | `integer` | no |  |

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Newsletter unsubscribes retrieved successfully | `ref:NewsletterFreeformResponse` |
| 401 |  |  |
| 429 |  |  |
| 500 |  |  |

## GET /api/public/v1/newsletters/unsubscribes/check/{email}
Check Newsletter Unsubscribe Status
Operation ID: `checkNewsletterUnsubscribeStatus`
Tags: `Newsletters`

### Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `email` | path | `string` | yes |  |

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Unsubscribe status retrieved successfully | `ref:NewsletterFreeformResponse` |
| 401 |  |  |
| 429 |  |  |
| 500 |  |  |

## POST /api/public/v1/newsletters/unsubscribes/manual
Manually Unsubscribe Newsletter Contact
Operation ID: `manuallyUnsubscribeNewsletterContact`
Tags: `Newsletters`

### Request Body
Required: yes
Schema: `object`

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Contact manually unsubscribed successfully | `ref:NewsletterSimpleSuccessResponse` |
| 400 |  |  |
| 401 |  |  |
| 429 |  |  |
| 500 |  |  |

## POST /api/public/v1/newsletters/unsubscribes/resubscribe
Resubscribe Newsletter Contact By Email
Operation ID: `resubscribeNewsletterContactByEmail`
Tags: `Newsletters`

### Request Body
Required: yes
Schema: `object`

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Contact resubscribed successfully | `ref:NewsletterSimpleSuccessResponse` |
| 400 |  |  |
| 401 |  |  |
| 403 |  |  |
| 429 |  |  |
| 500 |  |  |

## POST /api/public/v1/newsletters/unsubscribes/{unsubscribeUuid}/resubscribe
Resubscribe Newsletter Contact
Operation ID: `resubscribeNewsletterContact`
Tags: `Newsletters`

### Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `unsubscribeUuid` | path | `string` | yes |  |

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Contact resubscribed successfully | `ref:NewsletterSimpleSuccessResponse` |
| 401 |  |  |
| 404 |  |  |
| 429 |  |  |
| 500 |  |  |

## GET /api/woocommerce/orders
List Orders
Retrieve a list of orders for your WooCommerce store with optional filtering and pagination. **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120.
Operation ID: `listWooCommerceOrders`
Tags: `WooCommerce Orders`

### Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `status` | query | `string` | no | Filter by order status (free-form string, exact match). Recommended values: pending, processing, completed, cancelled, refunded, failed. |
| `date_from` | query | `string` | no | Filter orders from this date (YYYY-MM-DD) |
| `date_to` | query | `string` | no | Filter orders up to this date (YYYY-MM-DD) |
| `customer_search` | query | `string` | no | Search by customer name, email, or phone |
| `total_min` | query | `number` | no | Minimum total amount |
| `total_max` | query | `number` | no | Maximum total amount |
| `sort_by` | query | `string` | no | Field to sort by |
| `sort_direction` | query | `string` | no | Sort direction |
| `include_vouchers` | query | `boolean` | no | Include parsed voucher data in each order response item |
| `page` | query | `integer` | no | Page number (1-based) |
| `limit` | query | `integer` | no | Number of results per page |

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | List of orders retrieved successfully | `object` |
| 401 |  |  |
| 429 |  |  |
| 500 |  |  |

## POST /api/woocommerce/orders
Create Single Order
Import a single order from WooCommerce to Merchandillo. **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120. **Duplicate Handling:** If an order with the same `order_number` already exists for this store, the existing order will be updated instead of creating a new one.
Operation ID: `createWooCommerceOrder`
Tags: `WooCommerce Orders`

### Request Body
Required: yes
Schema: `#/components/schemas/OrderInput`
Example:
```json
{
  "id": 12345,
  "order_number": "WC-12345",
  "customer_name": "John Doe",
  "customer_email": "john.doe@example.com",
  "customer_phone": "+30 210 1234567",
  "total_amount": 150.5,
  "subtotal": 130,
  "tax_amount": 15.5,
  "shipping_amount": 5,
  "discount_amount": 0,
  "currency": "EUR",
  "status": "processing",
  "payment_method": "Credit Card",
  "payment_status": "paid",
  "shipping_method": "Standard Shipping",
  "tracking_number": "TRACK123456",
  "courier": "acs",
  "tracking_url": "https://tracking.example.com/TRACK123456",
  "voucher": {
    "courier": "acs",
    "tracking_number": "TRACK123456",
    "tracking_url": "https://tracking.example.com/TRACK123456",
    "voucher_number": "ACS-V-12345"
  },
  "order_date": "2025-11-13",
  "notes": "Customer requested gift wrapping",
  "shipping_address": {
    "first_name": "John",
    "last_name": "Doe",
    "address_1": "123 Main Street",
    "address_2": "Apt 4B",
    "city": "Athens",
    "postcode": "10431",
    "country": "Greece",
    "zone": "Attica"
  },
  "billing_address": {
    "first_name": "John",
    "last_name": "Doe",
    "address_1": "123 Main Street",
    "city": "Athens",
    "postcode": "10431",
    "country": "Greece"
  },
  "items": [
    {
      "product_id": 12345,
      "product_sku": "PROD-001",
      "product_name": "Wireless Mouse",
      "quantity": 2,
      "price": 25,
      "total": 50,
      "tax_amount": 5,
      "product_options": {
        "color": "Black"
      }
    }
  ]
}
```

### Responses
| Status | Description | Schema |
|---|---|---|
| 201 | Order created successfully | `ref:OrderResponse` |
| 400 |  |  |
| 401 |  |  |
| 429 |  |  |
| 500 |  |  |

## POST /api/woocommerce/orders/batch
Create Batch Orders
Import multiple orders in a single request for efficient bulk synchronization. **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120. **Batch Limits:** - Minimum: 1 order - Maximum: 200 orders per request - Recommended: 50-100 orders for optimal performance **Duplicate Handling:** Orders with duplicate `order_number` values (within the batch or against existing orders) will be updated instead of creating duplicates.
Operation ID: `createWooCommerceBatchOrders`
Tags: `WooCommerce Orders`

### Request Body
Required: yes
Schema: `object`

### Responses
| Status | Description | Schema |
|---|---|---|
| 201 | All orders created successfully | `ref:BatchOrderResponse` |
| 207 | Partial success - some orders failed | `ref:BatchOrderResponse` |
| 400 |  |  |
| 401 |  |  |
| 429 |  |  |
| 500 |  |  |

## GET /api/woocommerce/orders/{orderId}
Get Single Order
Retrieve a specific order by its UUID or `order_number`. **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120.
Operation ID: `getWooCommerceOrder`
Tags: `WooCommerce Orders`

### Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `orderId` | path | `string` | yes | Order UUID or `order_number` |

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Order retrieved successfully | `ref:OrderResponse` |
| 401 |  |  |
| 404 |  |  |
| 429 |  |  |
| 500 |  |  |

## PUT /api/woocommerce/orders/{orderId}
Update Order
Update an existing order. All fields are optional. **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120.
Operation ID: `updateWooCommerceOrder`
Tags: `WooCommerce Orders`

### Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `orderId` | path | `string` | yes | Order UUID |

### Request Body
Required: yes
Schema: `#/components/schemas/OrderUpdateInput`
Example:
```json
{
  "status": "completed",
  "tracking_number": "TRACK789456",
  "courier": "acs",
  "tracking_url": "https://tracking.example.com/TRACK789456",
  "voucher": {
    "courier": "acs",
    "tracking_number": "TRACK789456",
    "tracking_url": "https://tracking.example.com/TRACK789456",
    "voucher_number": "ACS-V-789456"
  },
  "notes": "Order delivered successfully"
}
```

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Order updated successfully | `ref:OrderResponse` |
| 400 |  |  |
| 401 |  |  |
| 404 |  |  |
| 429 |  |  |
| 500 |  |  |

## DELETE /api/woocommerce/orders/{orderId}
Delete Order
Delete an order from the system. **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120.
Operation ID: `deleteWooCommerceOrder`
Tags: `WooCommerce Orders`

### Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `orderId` | path | `string` | yes | Order UUID |

### Responses
| Status | Description | Schema |
|---|---|---|
| 200 | Order deleted successfully | `object` |
| 401 |  |  |
| 404 |  |  |
| 429 |  |  |
| 500 |  |  |

# Schemas

## Address

| Property | Type | Required | Description |
|---|---|---|---|
| `first_name` | `string` | no |  |
| `last_name` | `string` | no |  |
| `company` | `string` | no |  |
| `address_1` | `string` | no |  |
| `address_2` | `string` | no |  |
| `city` | `string` | no |  |
| `postcode` | `string` | no |  |
| `country` | `string` | no |  |
| `zone` | `string` | no | State/Province/Region |

## BatchOrderResponse

| Property | Type | Required | Description |
|---|---|---|---|
| `success` | `boolean` | no |  |
| `total_orders` | `integer` | no | Total number of orders in the batch |
| `successful` | `integer` | no | Number of successfully processed orders |
| `failed` | `integer` | no | Number of failed orders |
| `results` | `array<object>` | no |  |
| `store` | `object` | no |  |

## Error

| Property | Type | Required | Description |
|---|---|---|---|
| `success` | `boolean` | no |  |
| `error` | `string` | no |  |
| `details` | `string` | no | Additional error details |

## ExternalVoucher

| Property | Type | Required | Description |
|---|---|---|---|
| `uuid` | `string` | no |  |
| `order_uuid` | `string` | no |  |
| `tracking_number` | `string` | no |  |
| `shipping_provider` | `string` | no |  |
| `tracking_url` | `string` | no |  |
| `voucher_url` | `string` | no |  |
| `voucher_number` | `string` | no |  |
| `provider_data` | `object` | no |  |
| `created_at` | `string` | no |  |
| `updated_at` | `string` | no |  |

## ExternalVoucherInput
Optional voucher payload for imported orders. Data is stored in the existing `vouchers` table (no additional order columns required).

| Property | Type | Required | Description |
|---|---|---|---|
| `courier` | `string` | no | Courier/provider identifier (for example `acs`, `speedex`, `geniki`) |
| `tracking_number` | `string` | no | Tracking number assigned by courier |
| `tracking_url` | `string` | no | Public URL to track shipment progress |
| `voucher_number` | `string` | no | Provider-specific voucher/label number |
| `provider_data` | `object` | no | Additional provider-specific metadata stored under `shipping_provider_data` |

## NewsletterActionResponse

| Property | Type | Required | Description |
|---|---|---|---|
| `success` | `boolean` | no |  |
| `data` | `object` | no |  |
| `message` | `string` | no |  |

## NewsletterCampaign

| Property | Type | Required | Description |
|---|---|---|---|
| `uuid` | `string` | no |  |
| `store_uuid` | `string` | no |  |
| `user_uuid` | `string` | no |  |
| `name` | `string` | no |  |
| `subject` | `string` | no |  |
| `preview_text` | `string` | no |  |
| `smtp_sender_id` | `string` | no |  |
| `status` | `string` | no |  |
| `scheduled_at` | `string` | no |  |
| `sent_at` | `string` | no |  |
| `created_at` | `string` | no |  |
| `updated_at` | `string` | no |  |

## NewsletterCampaignCreateInput

| Property | Type | Required | Description |
|---|---|---|---|
| `name` | `string` | yes |  |
| `subject` | `string` | yes |  |
| `preview_text` | `string` | no |  |
| `smtp_sender_id` | `string` | no |  |
| `content_html` | `string` | no |  |
| `content_text` | `string` | no |  |
| `customer_group_ids` | `array<string>` | no |  |

## NewsletterCampaignListResponse

| Property | Type | Required | Description |
|---|---|---|---|
| `success` | `boolean` | no |  |
| `data` | `object` | no |  |
| `pagination` | `ref:Pagination` | no |  |

## NewsletterCampaignMutationResponse

| Property | Type | Required | Description |
|---|---|---|---|
| `success` | `boolean` | no |  |
| `data` | `object` | no |  |

## NewsletterCampaignResponse

| Property | Type | Required | Description |
|---|---|---|---|
| `success` | `boolean` | no |  |
| `data` | `ref:NewsletterCampaign` | no |  |

## NewsletterCampaignRun

| Property | Type | Required | Description |
|---|---|---|---|
| `run_uuid` | `string` | no |  |
| `started_at` | `string` | no |  |
| `last_activity_at` | `string` | no |  |
| `total` | `integer` | no |  |
| `pending_count` | `integer` | no |  |
| `processing_count` | `integer` | no |  |
| `sent_count` | `integer` | no |  |
| `failed_count` | `integer` | no |  |
| `cancelled_count` | `integer` | no |  |
| `retried_count` | `integer` | no |  |
| `pending_retry_count` | `integer` | no |  |
| `terminal_failed_count` | `integer` | no |  |

## NewsletterCampaignRunResponse

| Property | Type | Required | Description |
|---|---|---|---|
| `success` | `boolean` | no |  |
| `data` | `object` | no |  |

## NewsletterCampaignRunsResponse

| Property | Type | Required | Description |
|---|---|---|---|
| `success` | `boolean` | no |  |
| `data` | `object` | no |  |
| `pagination` | `ref:Pagination` | no |  |

## NewsletterCampaignUpdateInput

| Property | Type | Required | Description |
|---|---|---|---|
| `name` | `string` | no |  |
| `subject` | `string` | no |  |
| `preview_text` | `string` | no |  |
| `smtp_sender_id` | `string` | no |  |
| `content_html` | `string` | no |  |
| `content_text` | `string` | no |  |
| `customer_group_ids` | `array<string>` | no |  |

## NewsletterCustomer

| Property | Type | Required | Description |
|---|---|---|---|
| `customer_email` | `string` | no |  |
| `customer_name` | `string` | no |  |
| `order_count` | `integer` | no |  |
| `total_spent` | `number` | no |  |
| `average_order_value` | `number` | no |  |
| `first_order_date` | `string` | no |  |
| `last_order_date` | `string` | no |  |
| `status` | `string` | no |  |
| `exclusion_reason` | `string` | no |  |

## NewsletterCustomerSearchResponse

| Property | Type | Required | Description |
|---|---|---|---|
| `success` | `boolean` | no |  |
| `data` | `object` | no |  |

## NewsletterFreeformResponse

| Property | Type | Required | Description |
|---|---|---|---|
| `success` | `boolean` | no |  |
| `data` | `object` | no |  |
| `pagination` | `ref:Pagination` | no |  |

## NewsletterGroup

| Property | Type | Required | Description |
|---|---|---|---|
| `uuid` | `string` | no |  |
| `store_uuid` | `string` | no |  |
| `user_uuid` | `string` | no |  |
| `name` | `string` | no |  |
| `description` | `string` | no |  |
| `rules` | `ref:NewsletterSegmentRules` | no |  |
| `customer_count_cached` | `integer` | no | Active emailable members currently cached for the group. |
| `unsubscribed_count_cached` | `integer` | no | Matching customers currently excluded because they are unsubscribed. |
| `customer_count_updated_at` | `string` | no |  |
| `is_active` | `boolean` | no |  |
| `created_at` | `string` | no |  |
| `updated_at` | `string` | no |  |

## NewsletterGroupCreateInput

| Property | Type | Required | Description |
|---|---|---|---|
| `name` | `string` | yes |  |
| `description` | `string` | no |  |
| `rules` | `ref:NewsletterSegmentRules` | yes |  |

## NewsletterGroupCustomersResponse

| Property | Type | Required | Description |
|---|---|---|---|
| `success` | `boolean` | no |  |
| `data` | `object` | no |  |

## NewsletterGroupListResponse

| Property | Type | Required | Description |
|---|---|---|---|
| `success` | `boolean` | no |  |
| `data` | `object` | no |  |

## NewsletterGroupMutationResponse

| Property | Type | Required | Description |
|---|---|---|---|
| `success` | `boolean` | no |  |
| `data` | `object` | no |  |

## NewsletterGroupPreviewResponse

| Property | Type | Required | Description |
|---|---|---|---|
| `success` | `boolean` | no |  |
| `data` | `object` | no |  |

## NewsletterGroupResponse

| Property | Type | Required | Description |
|---|---|---|---|
| `success` | `boolean` | no |  |
| `data` | `ref:NewsletterGroup` | no |  |

## NewsletterGroupUpdateInput

| Property | Type | Required | Description |
|---|---|---|---|
| `name` | `string` | no |  |
| `description` | `string` | no |  |
| `rules` | `ref:NewsletterSegmentRules` | no |  |
| `is_active` | `boolean` | no |  |

## NewsletterProductSearchResponse

| Property | Type | Required | Description |
|---|---|---|---|
| `success` | `boolean` | no |  |
| `data` | `object` | no |  |

## NewsletterProductSuggestion

| Property | Type | Required | Description |
|---|---|---|---|
| `product_key` | `string` | no | Product identity key, chosen from product_id, product_sku, or product_name. |
| `match_type` | `string` | no |  |
| `product_id` | `string` | no |  |
| `product_sku` | `string` | no |  |
| `product_name` | `string` | no |  |
| `default_unit_price` | `number` | no |  |
| `last_order_date` | `string` | no |  |
| `used_count` | `integer` | no |  |

## NewsletterScheduleInput

| Property | Type | Required | Description |
|---|---|---|---|
| `scheduled_at` | `string` | yes |  |

## NewsletterSegmentCondition

| Property | Type | Required | Description |
|---|---|---|---|
| `field` | `string` | yes |  |
| `operator` | `string` | yes |  |
| `value` | `object` | no |  |

## NewsletterSegmentRules

| Property | Type | Required | Description |
|---|---|---|---|
| `conditions` | `array<ref:NewsletterSegmentCondition>` | yes |  |
| `operator` | `string` | yes |  |

## NewsletterSendPreviewInput

| Property | Type | Required | Description |
|---|---|---|---|
| `campaign_uuid` | `string` | no |  |
| `customer_email` | `string` | no |  |
| `customer_name` | `string` | no |  |
| `variables` | `object` | no |  |

## NewsletterSendTestInput

| Property | Type | Required | Description |
|---|---|---|---|
| `campaign_uuid` | `string` | yes |  |
| `email` | `string` | yes |  |
| `customer_name` | `string` | no |  |

## NewsletterSimpleSuccessResponse

| Property | Type | Required | Description |
|---|---|---|---|
| `success` | `boolean` | no |  |

## NewsletterTemplate

| Property | Type | Required | Description |
|---|---|---|---|
| `uuid` | `string` | no |  |
| `store_uuid` | `string` | no |  |
| `user_uuid` | `string` | no |  |
| `name` | `string` | no |  |
| `description` | `string` | no |  |
| `category` | `string` | no |  |
| `content_html` | `string` | no |  |
| `content_text` | `string` | no |  |
| `created_at` | `string` | no |  |
| `updated_at` | `string` | no |  |

## NewsletterTemplateCreateResponse

| Property | Type | Required | Description |
|---|---|---|---|
| `success` | `boolean` | no |  |
| `data` | `object` | no |  |

## NewsletterTemplateInput

| Property | Type | Required | Description |
|---|---|---|---|
| `name` | `string` | yes |  |
| `description` | `string` | no |  |
| `category` | `string` | no |  |
| `content_html` | `string` | no |  |
| `content_text` | `string` | no |  |

## NewsletterTemplateListResponse

| Property | Type | Required | Description |
|---|---|---|---|
| `success` | `boolean` | no |  |
| `data` | `object` | no |  |

## Order

| Property | Type | Required | Description |
|---|---|---|---|
| `uuid` | `string` | no | Unique identifier for the order |
| `store_uuid` | `string` | no | Store UUID this order belongs to |
| `order_number` | `string` | no |  |
| `customer_name` | `string` | no |  |
| `customer_email` | `string` | no |  |
| `customer_phone` | `string` | no |  |
| `total_amount` | `number` | no |  |
| `subtotal` | `number` | no |  |
| `tax_amount` | `number` | no |  |
| `shipping_amount` | `number` | no |  |
| `discount_amount` | `number` | no |  |
| `currency` | `string` | no |  |
| `status` | `string` | no |  |
| `payment_method` | `string` | no |  |
| `payment_status` | `string` | no |  |
| `shipping_method` | `string` | no |  |
| `tracking_number` | `string` | no |  |
| `order_date` | `string` | no |  |
| `shipped_at` | `string` | no |  |
| `delivered_at` | `string` | no |  |
| `notes` | `string` | no |  |
| `internal_notes` | `string` | no |  |
| `shipping_address` | `ref:Address` | no |  |
| `billing_address` | `ref:Address` | no |  |
| `items` | `array<ref:OrderItem>` | no |  |
| `tags` | `array<ref:Tag>` | no |  |
| `vouchers` | `array<ref:ExternalVoucher>` | no |  |
| `created_at` | `string` | no |  |
| `updated_at` | `string` | no |  |

## OrderInput

| Property | Type | Required | Description |
|---|---|---|---|
| `order_number` | `string` | yes | Unique order number from your store |
| `customer_name` | `string` | yes | Full name of the customer |
| `customer_email` | `string` | no | Customer email address |
| `customer_phone` | `string` | no | Customer phone number |
| `total_amount` | `number` | yes | Total order amount including taxes and shipping |
| `subtotal` | `number` | no | Subtotal before taxes and shipping |
| `tax_amount` | `number` | no | Total tax amount |
| `shipping_amount` | `number` | no | Shipping cost |
| `discount_amount` | `number` | no | Discount amount applied |
| `currency` | `string` | no | Currency code (ISO 4217) |
| `status` | `string` | no | Order status (free-form string, max 255 chars). Recommended values: pending, processing, completed, cancelled, refunded, failed. |
| `payment_method` | `string` | no | Payment method used |
| `payment_status` | `string` | no | Payment status (free-form string, max 255 chars). Recommended values: pending, paid, failed, refunded. |
| `shipping_method` | `string` | no | Shipping method selected |
| `tracking_number` | `string` | no | Shipping tracking number |
| `courier` | `string` | no | Courier/provider identifier for imported shipment vouchers. When provided with `voucher` or `tracking_url`, voucher data is stored in the `vouchers` table. |
| `tracking_url` | `string` | no | Public tracking URL for this shipment |
| `voucher` | `ref:ExternalVoucherInput` | no |  |
| `tags` | `array<ref:TagInput>` | no | Optional order tags to create/reuse and attach to the order |
| `order_date` | `string` | no | Date when order was placed (YYYY-MM-DD). Current runtime behavior on OpenCart, WooCommerce, and custom create/batch insert paths also accepts: - `2025-11-13 16:30:00` - `2025-11-13T16:30:00` - `2025-11-13T16:30:00+00:00` - `2025-11-13T16:30:00+02:00` (normalized by MySQL to UTC before storage) |
| `shipped_at` | `string` | no | Timestamp when the order was shipped |
| `delivered_at` | `string` | no | Timestamp when the order was delivered |
| `notes` | `string` | no | Customer-facing notes |
| `internal_notes` | `string` | no | Internal notes (not visible to customer) |
| `shipping_address` | `ref:Address` | no |  |
| `billing_address` | `ref:Address` | no |  |
| `items` | `array<ref:OrderItem>` | no | Order line items |
| `raw_data` | `object` | no | Raw order data from OpenCart/WooCommerce for reference |

## OrderItem

| Property | Type | Required | Description |
|---|---|---|---|
| `product_sku` | `string` | no | Product SKU/Code |
| `product_name` | `string` | yes | Product name |
| `product_id` | `string` | no | Product ID from your store |
| `quantity` | `integer` | yes | Quantity ordered |
| `price` | `number` | yes | Unit price |
| `total_price` | `number` | no | Total price (quantity × price) |
| `tax_amount` | `number` | no | Tax amount for this item |
| `discount_amount` | `number` | no | Discount amount for this item |
| `product_options` | `object` | no | Product options/variations (e.g., color, size) |
| `product_metadata` | `object` | no | Additional product metadata |

## OrderResponse

| Property | Type | Required | Description |
|---|---|---|---|
| `success` | `boolean` | no |  |
| `data` | `ref:Order` | no |  |
| `message` | `string` | no |  |

## OrderUpdateInput
All fields are optional for updates

| Property | Type | Required | Description |
|---|---|---|---|
| `customer_name` | `string` | no |  |
| `customer_email` | `string` | no |  |
| `customer_phone` | `string` | no |  |
| `total_amount` | `number` | no |  |
| `status` | `string` | no | Order status (free-form string, max 255 chars). Recommended values: pending, processing, completed, cancelled, refunded, failed. |
| `payment_status` | `string` | no | Payment status (free-form string, max 255 chars). Recommended values: pending, paid, failed, refunded. |
| `tracking_number` | `string` | no | Shipping tracking number |
| `shipping_method` | `string` | no | Shipping method selected |
| `courier` | `string` | no | Courier/provider identifier for imported shipment vouchers |
| `tracking_url` | `string` | no | Public tracking URL for this shipment |
| `voucher` | `ref:ExternalVoucherInput` | no |  |
| `shipped_at` | `string` | no | Timestamp when the order was shipped |
| `delivered_at` | `string` | no | Timestamp when the order was delivered |
| `tags` | `array<ref:TagInput>` | no | Optional order tags for this order. Pass `null` to clear all tags from the order. |
| `notes` | `string` | no |  |
| `internal_notes` | `string` | no |  |

## Pagination

| Property | Type | Required | Description |
|---|---|---|---|
| `page` | `integer` | no |  |
| `limit` | `integer` | no |  |
| `total` | `integer` | no |  |
| `pages` | `integer` | no |  |

## Tag

| Property | Type | Required | Description |
|---|---|---|---|
| `uuid` | `string` | no |  |
| `name` | `string` | no |  |
| `slug` | `string` | no |  |
| `color` | `string` | no |  |

## TagInput

| Property | Type | Required | Description |
|---|---|---|---|
| `label` | `string` | yes | Tag label |
| `color` | `string` | no | Optional tag color (for example `oklch(...)` or `var(--tag-color)`) |

# Full OpenAPI Source

```yaml
openapi: 3.0.3
info:
  title: Merchandillo External API
  version: 1.0.0
  description: |
    The Merchandillo External API allows you to synchronize orders from your e-commerce store
    to the Merchandillo platform and manage newsletter operations for that store.

    ## Supported Store Types

    - **OpenCart**: Fully supported with dedicated endpoints at `/api/opencart/orders/*`
    - **WooCommerce**: Fully supported with dedicated endpoints at `/api/woocommerce/orders/*`
    - **Custom Integration**: Fully supported with dedicated endpoints at `/api/custom/orders/*`
    - **Newsletter Management**: Store-scoped public endpoints at `/api/public/v1/newsletters/*`

    Each store type has its own endpoints to accommodate different tracking requirements and special rules.
    
    ## Authentication
    
    All endpoints require API Key and API Secret authentication. You can obtain these credentials 
    from your store settings in the Merchandillo dashboard.
    
    **Authentication Methods:**
    1. Authorization header: `Authorization: Bearer API_KEY:API_SECRET` (Recommended)
    2. Custom headers: `X-API-Key` and `X-API-Secret`
    3. Query parameters: `api_key` and `api_secret` (Testing only)
    
    ## Rate Limits
    
    - **Window:** 60 seconds
    - **Free plan:** 200 single operations, 20 batch operations
    - **Plus plan:** 600 single operations, 60 batch operations
    - **Pro plan:** 1200 single operations, 120 batch operations
    - **Custom integration endpoints (`/api/custom/orders/*`) use the OpenCart limits**
    
    When rate limit is exceeded, you'll receive a 429 status code with a `Retry-After` header 
    indicating how many seconds to wait before retrying.

    ## Status Values

    Order and payment statuses are stored verbatim as free-form strings (max 255 characters).
    You can send any platform-specific value. For consistency, we recommend the following
    defaults: `pending`, `processing`, `completed`, `cancelled`, `refunded`, `failed` for
    order status and `pending`, `paid`, `failed`, `refunded` for payment status.

  contact:
    name: Merchandillo Support
    email: contact@merchandillo.com
  license:
    name: Proprietary
    
servers:
  - url: https://data.merchandillo.com
    description: Merchandillo API

security:
  - bearerAuth: []
  - apiKeyHeader: []

paths:
  /api/opencart/orders:
    post:
      summary: Create Single Order
      description: |
        Import a single order from OpenCart/WooCommerce to Merchandillo.
        
        **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120.
        
        **Duplicate Handling:** If an order with the same `order_number` already exists for this store, 
        the existing order will be updated instead of creating a new one.
      operationId: createOrder
      tags:
        - OpenCart Orders
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OrderInput'
            example:
              order_number: "12345"
              customer_name: "John Doe"
              customer_email: "john.doe@example.com"
              customer_phone: "+30 210 1234567"
              total_amount: 150.50
              subtotal: 130.00
              tax_amount: 15.50
              shipping_amount: 5.00
              discount_amount: 0.00
              currency: "EUR"
              status: "processing"
              payment_method: "Credit Card"
              payment_status: "paid"
              shipping_method: "Standard Shipping"
              tracking_number: "TRACK123456"
              courier: "acs"
              tracking_url: "https://tracking.example.com/TRACK123456"
              voucher:
                courier: "acs"
                tracking_number: "TRACK123456"
                tracking_url: "https://tracking.example.com/TRACK123456"
                voucher_number: "ACS-V-12345"
              order_date: "2025-11-13"
              notes: "Customer requested gift wrapping"
              shipping_address:
                first_name: "John"
                last_name: "Doe"
                address_1: "123 Main Street"
                address_2: "Apt 4B"
                city: "Athens"
                postcode: "10431"
                country: "Greece"
                zone: "Attica"
              billing_address:
                first_name: "John"
                last_name: "Doe"
                address_1: "123 Main Street"
                city: "Athens"
                postcode: "10431"
                country: "Greece"
              items:
                - product_sku: "PROD-001"
                  product_name: "Wireless Mouse"
                  product_id: "12345"
                  quantity: 2
                  price: 25.00
                  total_price: 50.00
                  tax_amount: 5.00
                  product_options:
                    color: "Black"
      responses:
        '201':
          description: Order created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderResponse'
              example:
                success: true
                data:
                  uuid: "01934567-89ab-7cde-f012-3456789abcde"
                  order_number: "12345"
                  customer_name: "John Doe"
                  total_amount: 150.50
                  status: "processing"
                  created_at: "2025-11-13T14:30:00Z"
                message: "Order created successfully"
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'
          
    get:
      summary: List Orders
      description: |
        Retrieve a list of orders for your store with optional filtering and pagination.
        
        **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120.
      operationId: listOrders
      tags:
        - OpenCart Orders
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: status
          in: query
          description: |
            Filter by order status (free-form string, exact match).
            Recommended values: pending, processing, completed, cancelled, refunded, failed.
          schema:
            type: string
            maxLength: 255
        - name: date_from
          in: query
          description: Filter orders from this date (YYYY-MM-DD)
          schema:
            type: string
            format: date
        - name: date_to
          in: query
          description: Filter orders up to this date (YYYY-MM-DD)
          schema:
            type: string
            format: date
        - name: customer_search
          in: query
          description: Search by customer name, email, or phone
          schema:
            type: string
        - name: total_min
          in: query
          description: Minimum total amount
          schema:
            type: number
            format: float
        - name: total_max
          in: query
          description: Maximum total amount
          schema:
            type: number
            format: float
        - name: sort_by
          in: query
          description: Field to sort by
          schema:
            type: string
            enum: [created_at, order_date, total_amount, customer_name, status, order_number]
            default: created_at
        - name: sort_direction
          in: query
          description: Sort direction
          schema:
            type: string
            enum: [asc, desc]
            default: desc
        - name: include_vouchers
          in: query
          description: Include parsed voucher data in each order response item
          schema:
            type: boolean
            default: false
        - name: page
          in: query
          description: Page number (1-based)
          schema:
            type: integer
            minimum: 1
            default: 1
        - name: limit
          in: query
          description: Number of results per page
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
      responses:
        '200':
          description: List of orders retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Order'
                  pagination:
                    $ref: '#/components/schemas/Pagination'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'
  
  /api/opencart/orders/{orderId}:
    get:
      summary: Get Single Order
      description: |
        Retrieve a specific order by its UUID or `order_number`.
        
        **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120.
      operationId: getOrder
      tags:
        - OpenCart Orders
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: orderId
          in: path
          required: true
          description: Order UUID or `order_number`
          schema:
            type: string
            description: Order identifier as UUID or `order_number`
            example: "12345"
      responses:
        '200':
          description: Order retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'
          
    put:
      summary: Update Order
      description: |
        Update an existing order. All fields are optional.
        
        **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120.
      operationId: updateOrder
      tags:
        - OpenCart Orders
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: orderId
          in: path
          required: true
          description: Order UUID
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OrderUpdateInput'
            example:
              status: "completed"
              tracking_number: "TRACK789456"
              courier: "acs"
              tracking_url: "https://tracking.example.com/TRACK789456"
              voucher:
                courier: "acs"
                tracking_number: "TRACK789456"
                tracking_url: "https://tracking.example.com/TRACK789456"
                voucher_number: "ACS-V-789456"
              notes: "Order delivered successfully"
      responses:
        '200':
          description: Order updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'
          
    delete:
      summary: Delete Order
      description: |
        Delete an order from the system.
        
        **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120.
      operationId: deleteOrder
      tags:
        - OpenCart Orders
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: orderId
          in: path
          required: true
          description: Order UUID
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Order deleted successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: "Order deleted successfully"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'
  
  /api/opencart/orders/batch:
    post:
      summary: Create Batch Orders
      description: |
        Import multiple orders in a single request for efficient bulk synchronization.
        
        **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120.
        
        **Batch Limits:**
        - Minimum: 1 order
        - Maximum: 200 orders per request
        - Recommended: 50-100 orders for optimal performance
        
        **Duplicate Handling:** Orders with duplicate `order_number` values (within the batch or 
        against existing orders) will be updated instead of creating duplicates.
      operationId: createBatchOrders
      tags:
        - OpenCart Orders
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - type: array
                  items:
                    $ref: '#/components/schemas/OrderInput'
                  minItems: 1
                  maxItems: 200
                - type: object
                  properties:
                    orders:
                      type: array
                      items:
                        $ref: '#/components/schemas/OrderInput'
                      minItems: 1
                      maxItems: 200
                  required:
                    - orders
            examples:
              directArray:
                summary: Direct array of orders
                value:
                  - order_number: "12345"
                    customer_name: "John Doe"
                    total_amount: 150.50
                    currency: "EUR"
                    status: "processing"
                  - order_number: "12346"
                    customer_name: "Jane Smith"
                    total_amount: 89.99
                    currency: "EUR"
                    status: "pending"
              ordersProperty:
                summary: Object with orders property
                value:
                  orders:
                    - order_number: "12345"
                      customer_name: "John Doe"
                      total_amount: 150.50
                      currency: "EUR"
                      status: "processing"
                    - order_number: "12346"
                      customer_name: "Jane Smith"
                      total_amount: 89.99
                      currency: "EUR"
                      status: "pending"
      responses:
        '201':
          description: All orders created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BatchOrderResponse'
              example:
                success: true
                total_orders: 2
                successful: 2
                failed: 0
                results:
                  - order_number: "12345"
                    success: true
                    order_uuid: "01934567-89ab-7cde-f012-3456789abcde"
                    message: "Order created successfully"
                  - order_number: "12346"
                    success: true
                    order_uuid: "01934567-89ab-7cde-f012-3456789abcdf"
                    message: "Order created successfully"
                store:
                  uuid: "01912345-678a-7bcd-e012-3456789abcde"
                  name: "My OpenCart Store"
                  type: "opencart"
        '207':
          description: Partial success - some orders failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BatchOrderResponse'
              example:
                success: true
                total_orders: 3
                successful: 2
                failed: 1
                results:
                  - order_number: "12345"
                    success: true
                    order_uuid: "01934567-89ab-7cde-f012-3456789abcde"
                    message: "Order created successfully"
                  - order_number: "12346"
                    success: false
                    error: "Order number already exists for this store"
                  - order_number: "12347"
                    success: true
                    order_uuid: "01934567-89ab-7cde-f012-3456789abcdf"
                    message: "Order created successfully"
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/custom/orders:
    post:
      summary: Create Single Order
      description: |
        Import a single order from Custom to Merchandillo.
        
        **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120.
        
        **Duplicate Handling:** If an order with the same `order_number` already exists for this store, 
        the existing order will be updated instead of creating a new one.
      operationId: createCustomOrder
      tags:
        - Custom Orders
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OrderInput'
            example:
              order_number: "12345"
              customer_name: "John Doe"
              customer_email: "john.doe@example.com"
              customer_phone: "+30 210 1234567"
              total_amount: 150.50
              subtotal: 130.00
              tax_amount: 15.50
              shipping_amount: 5.00
              discount_amount: 0.00
              currency: "EUR"
              status: "processing"
              payment_method: "Credit Card"
              payment_status: "paid"
              shipping_method: "Standard Shipping"
              tracking_number: "TRACK123456"
              courier: "acs"
              tracking_url: "https://tracking.example.com/TRACK123456"
              voucher:
                courier: "acs"
                tracking_number: "TRACK123456"
                tracking_url: "https://tracking.example.com/TRACK123456"
                voucher_number: "ACS-V-12345"
              order_date: "2025-11-13"
              notes: "Customer requested gift wrapping"
              shipping_address:
                first_name: "John"
                last_name: "Doe"
                address_1: "123 Main Street"
                address_2: "Apt 4B"
                city: "Athens"
                postcode: "10431"
                country: "Greece"
                zone: "Attica"
              billing_address:
                first_name: "John"
                last_name: "Doe"
                address_1: "123 Main Street"
                city: "Athens"
                postcode: "10431"
                country: "Greece"
              items:
                - product_sku: "PROD-001"
                  product_name: "Wireless Mouse"
                  product_id: "12345"
                  quantity: 2
                  price: 25.00
                  total_price: 50.00
                  tax_amount: 5.00
                  product_options:
                    color: "Black"
      responses:
        '201':
          description: Order created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderResponse'
              example:
                success: true
                data:
                  uuid: "01934567-89ab-7cde-f012-3456789abcde"
                  order_number: "12345"
                  customer_name: "John Doe"
                  total_amount: 150.50
                  status: "processing"
                  created_at: "2025-11-13T14:30:00Z"
                message: "Order created successfully"
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'
          
    get:
      summary: List Orders
      description: |
        Retrieve a list of orders for your store with optional filtering and pagination.
        
        **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120.
      operationId: listCustomOrders
      tags:
        - Custom Orders
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: status
          in: query
          description: |
            Filter by order status (free-form string, exact match).
            Recommended values: pending, processing, completed, cancelled, refunded, failed.
          schema:
            type: string
            maxLength: 255
        - name: date_from
          in: query
          description: Filter orders from this date (YYYY-MM-DD)
          schema:
            type: string
            format: date
        - name: date_to
          in: query
          description: Filter orders up to this date (YYYY-MM-DD)
          schema:
            type: string
            format: date
        - name: customer_search
          in: query
          description: Search by customer name, email, or phone
          schema:
            type: string
        - name: total_min
          in: query
          description: Minimum total amount
          schema:
            type: number
            format: float
        - name: total_max
          in: query
          description: Maximum total amount
          schema:
            type: number
            format: float
        - name: sort_by
          in: query
          description: Field to sort by
          schema:
            type: string
            enum: [created_at, order_date, total_amount, customer_name, status, order_number]
            default: created_at
        - name: sort_direction
          in: query
          description: Sort direction
          schema:
            type: string
            enum: [asc, desc]
            default: desc
        - name: include_vouchers
          in: query
          description: Include parsed voucher data in each order response item
          schema:
            type: boolean
            default: false
        - name: page
          in: query
          description: Page number (1-based)
          schema:
            type: integer
            minimum: 1
            default: 1
        - name: limit
          in: query
          description: Number of results per page
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
      responses:
        '200':
          description: List of orders retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Order'
                  pagination:
                    $ref: '#/components/schemas/Pagination'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'
  
  /api/custom/orders/{orderId}:
    get:
      summary: Get Single Order
      description: |
        Retrieve a specific order by its UUID or `order_number`.
        
        **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120.
      operationId: getCustomOrder
      tags:
        - Custom Orders
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: orderId
          in: path
          required: true
          description: Order UUID or `order_number`
          schema:
            type: string
            description: Order identifier as UUID or `order_number`
            example: "12345"
      responses:
        '200':
          description: Order retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'
          
    put:
      summary: Update Order
      description: |
        Update an existing order. All fields are optional.
        
        **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120.
      operationId: updateCustomOrder
      tags:
        - Custom Orders
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: orderId
          in: path
          required: true
          description: Order UUID
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OrderUpdateInput'
            example:
              status: "completed"
              tracking_number: "TRACK789456"
              courier: "acs"
              tracking_url: "https://tracking.example.com/TRACK789456"
              voucher:
                courier: "acs"
                tracking_number: "TRACK789456"
                tracking_url: "https://tracking.example.com/TRACK789456"
                voucher_number: "ACS-V-789456"
              notes: "Order delivered successfully"
      responses:
        '200':
          description: Order updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'
          
    delete:
      summary: Delete Order
      description: |
        Delete an order from the system.
        
        **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120.
      operationId: deleteCustomOrder
      tags:
        - Custom Orders
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: orderId
          in: path
          required: true
          description: Order UUID
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Order deleted successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: "Order deleted successfully"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'
  
  /api/custom/orders/batch:
    post:
      summary: Create Batch Orders
      description: |
        Import multiple orders in a single request for efficient bulk synchronization.
        
        **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120.
        
        **Batch Limits:**
        - Minimum: 1 order
        - Maximum: 200 orders per request
        - Recommended: 50-100 orders for optimal performance
        
        **Duplicate Handling:** Orders with duplicate `order_number` values (within the batch or 
        against existing orders) will be updated instead of creating duplicates.
      operationId: createCustomBatchOrders
      tags:
        - Custom Orders
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - type: array
                  items:
                    $ref: '#/components/schemas/OrderInput'
                  minItems: 1
                  maxItems: 200
                - type: object
                  properties:
                    orders:
                      type: array
                      items:
                        $ref: '#/components/schemas/OrderInput'
                      minItems: 1
                      maxItems: 200
                  required:
                    - orders
            examples:
              directArray:
                summary: Direct array of orders
                value:
                  - order_number: "12345"
                    customer_name: "John Doe"
                    total_amount: 150.50
                    currency: "EUR"
                    status: "processing"
                  - order_number: "12346"
                    customer_name: "Jane Smith"
                    total_amount: 89.99
                    currency: "EUR"
                    status: "pending"
              ordersProperty:
                summary: Object with orders property
                value:
                  orders:
                    - order_number: "12345"
                      customer_name: "John Doe"
                      total_amount: 150.50
                      currency: "EUR"
                      status: "processing"
                    - order_number: "12346"
                      customer_name: "Jane Smith"
                      total_amount: 89.99
                      currency: "EUR"
                      status: "pending"
      responses:
        '201':
          description: All orders created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BatchOrderResponse'
              example:
                success: true
                total_orders: 2
                successful: 2
                failed: 0
                results:
                  - order_number: "12345"
                    success: true
                    order_uuid: "01934567-89ab-7cde-f012-3456789abcde"
                    message: "Order created successfully"
                  - order_number: "12346"
                    success: true
                    order_uuid: "01934567-89ab-7cde-f012-3456789abcdf"
                    message: "Order created successfully"
                store:
                  uuid: "01912345-678a-7bcd-e012-3456789abcde"
                  name: "My Custom Store"
                  type: "custom"
        '207':
          description: Partial success - some orders failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BatchOrderResponse'
              example:
                success: true
                total_orders: 3
                successful: 2
                failed: 1
                results:
                  - order_number: "12345"
                    success: true
                    order_uuid: "01934567-89ab-7cde-f012-3456789abcde"
                    message: "Order created successfully"
                  - order_number: "12346"
                    success: false
                    error: "Order number already exists for this store"
                  - order_number: "12347"
                    success: true
                    order_uuid: "01934567-89ab-7cde-f012-3456789abcdf"
                    message: "Order created successfully"
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/woocommerce/orders:
    post:
      summary: Create Single Order
      description: |
        Import a single order from WooCommerce to Merchandillo.

        **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120.

        **Duplicate Handling:** If an order with the same `order_number` already exists for this store,
        the existing order will be updated instead of creating a new one.
      operationId: createWooCommerceOrder
      tags:
        - WooCommerce Orders
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OrderInput'
            example:
              id: 12345
              order_number: "WC-12345"
              customer_name: "John Doe"
              customer_email: "john.doe@example.com"
              customer_phone: "+30 210 1234567"
              total_amount: 150.50
              subtotal: 130.00
              tax_amount: 15.50
              shipping_amount: 5.00
              discount_amount: 0.00
              currency: "EUR"
              status: "processing"
              payment_method: "Credit Card"
              payment_status: "paid"
              shipping_method: "Standard Shipping"
              tracking_number: "TRACK123456"
              courier: "acs"
              tracking_url: "https://tracking.example.com/TRACK123456"
              voucher:
                courier: "acs"
                tracking_number: "TRACK123456"
                tracking_url: "https://tracking.example.com/TRACK123456"
                voucher_number: "ACS-V-12345"
              order_date: "2025-11-13"
              notes: "Customer requested gift wrapping"
              shipping_address:
                first_name: "John"
                last_name: "Doe"
                address_1: "123 Main Street"
                address_2: "Apt 4B"
                city: "Athens"
                postcode: "10431"
                country: "Greece"
                zone: "Attica"
              billing_address:
                first_name: "John"
                last_name: "Doe"
                address_1: "123 Main Street"
                city: "Athens"
                postcode: "10431"
                country: "Greece"
              items:
                - product_id: 12345
                  product_sku: "PROD-001"
                  product_name: "Wireless Mouse"
                  quantity: 2
                  price: 25.00
                  total: 50.00
                  tax_amount: 5.00
                  product_options:
                    color: "Black"
      responses:
        '201':
          description: Order created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderResponse'
              example:
                success: true
                data:
                  uuid: "01934567-89ab-7cde-f012-3456789abcde"
                  order_number: "WC-12345"
                  customer_name: "John Doe"
                  total_amount: 150.50
                  status: "processing"
                  created_at: "2025-11-13T14:30:00Z"
                message: "Order created successfully"
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

    get:
      summary: List Orders
      description: |
        Retrieve a list of orders for your WooCommerce store with optional filtering and pagination.

        **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120.
      operationId: listWooCommerceOrders
      tags:
        - WooCommerce Orders
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: status
          in: query
          description: |
            Filter by order status (free-form string, exact match).
            Recommended values: pending, processing, completed, cancelled, refunded, failed.
          schema:
            type: string
            maxLength: 255
        - name: date_from
          in: query
          description: Filter orders from this date (YYYY-MM-DD)
          schema:
            type: string
            format: date
        - name: date_to
          in: query
          description: Filter orders up to this date (YYYY-MM-DD)
          schema:
            type: string
            format: date
        - name: customer_search
          in: query
          description: Search by customer name, email, or phone
          schema:
            type: string
        - name: total_min
          in: query
          description: Minimum total amount
          schema:
            type: number
            format: float
        - name: total_max
          in: query
          description: Maximum total amount
          schema:
            type: number
            format: float
        - name: sort_by
          in: query
          description: Field to sort by
          schema:
            type: string
            enum: [created_at, order_date, total_amount, customer_name, status, order_number]
            default: created_at
        - name: sort_direction
          in: query
          description: Sort direction
          schema:
            type: string
            enum: [asc, desc]
            default: desc
        - name: include_vouchers
          in: query
          description: Include parsed voucher data in each order response item
          schema:
            type: boolean
            default: false
        - name: page
          in: query
          description: Page number (1-based)
          schema:
            type: integer
            minimum: 1
            default: 1
        - name: limit
          in: query
          description: Number of results per page
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
      responses:
        '200':
          description: List of orders retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Order'
                  pagination:
                    $ref: '#/components/schemas/Pagination'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/woocommerce/orders/{orderId}:
    get:
      summary: Get Single Order
      description: |
        Retrieve a specific order by its UUID or `order_number`.

        **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120.
      operationId: getWooCommerceOrder
      tags:
        - WooCommerce Orders
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: orderId
          in: path
          required: true
          description: Order UUID or `order_number`
          schema:
            type: string
            description: Order identifier as UUID or `order_number`
            example: "12345"
      responses:
        '200':
          description: Order retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

    put:
      summary: Update Order
      description: |
        Update an existing order. All fields are optional.

        **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120.
      operationId: updateWooCommerceOrder
      tags:
        - WooCommerce Orders
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: orderId
          in: path
          required: true
          description: Order UUID
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OrderUpdateInput'
            example:
              status: "completed"
              tracking_number: "TRACK789456"
              courier: "acs"
              tracking_url: "https://tracking.example.com/TRACK789456"
              voucher:
                courier: "acs"
                tracking_number: "TRACK789456"
                tracking_url: "https://tracking.example.com/TRACK789456"
                voucher_number: "ACS-V-789456"
              notes: "Order delivered successfully"
      responses:
        '200':
          description: Order updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

    delete:
      summary: Delete Order
      description: |
        Delete an order from the system.

        **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120.
      operationId: deleteWooCommerceOrder
      tags:
        - WooCommerce Orders
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: orderId
          in: path
          required: true
          description: Order UUID
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Order deleted successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: "Order deleted successfully"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/woocommerce/orders/batch:
    post:
      summary: Create Batch Orders
      description: |
        Import multiple orders in a single request for efficient bulk synchronization.

        **Rate Limit:** Plan-aware (60s window): free 200 single/20 batch, plus 600/60, pro 1200/120.

        **Batch Limits:**
        - Minimum: 1 order
        - Maximum: 200 orders per request
        - Recommended: 50-100 orders for optimal performance

        **Duplicate Handling:** Orders with duplicate `order_number` values (within the batch or
        against existing orders) will be updated instead of creating duplicates.
      operationId: createWooCommerceBatchOrders
      tags:
        - WooCommerce Orders
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - type: array
                  items:
                    $ref: '#/components/schemas/OrderInput'
                  minItems: 1
                  maxItems: 200
                - type: object
                  properties:
                    orders:
                      type: array
                      items:
                        $ref: '#/components/schemas/OrderInput'
                      minItems: 1
                      maxItems: 200
                  required:
                    - orders
            examples:
              directArray:
                summary: Direct array of orders
                value:
                  - id: 12345
                    order_number: "WC-12345"
                    customer_name: "John Doe"
                    total_amount: 150.50
                    currency: "EUR"
                    status: "processing"
                  - id: 12346
                    order_number: "WC-12346"
                    customer_name: "Jane Smith"
                    total_amount: 89.99
                    currency: "EUR"
                    status: "pending"
              ordersProperty:
                summary: Object with orders property
                value:
                  orders:
                    - id: 12345
                      order_number: "WC-12345"
                      customer_name: "John Doe"
                      total_amount: 150.50
                      currency: "EUR"
                      status: "processing"
                    - id: 12346
                      order_number: "WC-12346"
                      customer_name: "Jane Smith"
                      total_amount: 89.99
                      currency: "EUR"
                      status: "pending"
      responses:
        '201':
          description: All orders created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BatchOrderResponse'
              example:
                success: true
                total_orders: 2
                successful: 2
                failed: 0
                results:
                  - order_number: "WC-12345"
                    success: true
                    order_uuid: "01934567-89ab-7cde-f012-3456789abcde"
                    message: "Order created successfully"
                  - order_number: "WC-12346"
                    success: true
                    order_uuid: "01934567-89ab-7cde-f012-3456789abcdf"
                    message: "Order created successfully"
                store:
                  uuid: "01912345-678a-7bcd-e012-3456789abcde"
                  name: "My WooCommerce Store"
                  type: "woocommerce"
        '207':
          description: Partial success - some orders failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BatchOrderResponse'
              example:
                success: true
                total_orders: 3
                successful: 2
                failed: 1
                results:
                  - order_number: "WC-12345"
                    success: true
                    order_uuid: "01934567-89ab-7cde-f012-3456789abcde"
                    message: "Order created successfully"
                  - order_number: "WC-12346"
                    success: false
                    error: "Order number already exists for this store"
                  - order_number: "WC-12347"
                    success: true
                    order_uuid: "01934567-89ab-7cde-f012-3456789abcdf"
                    message: "Order created successfully"
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/campaigns:
    get:
      summary: List Newsletter Campaigns
      operationId: listNewsletterCampaigns
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      responses:
        '200':
          description: Newsletter campaigns retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterCampaignListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'
    post:
      summary: Create Newsletter Campaign
      operationId: createNewsletterCampaign
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NewsletterCampaignCreateInput'
      responses:
        '201':
          description: Newsletter campaign created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterCampaignMutationResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/campaigns/{campaignUuid}:
    parameters:
      - name: campaignUuid
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      summary: Get Newsletter Campaign
      operationId: getNewsletterCampaign
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      responses:
        '200':
          description: Newsletter campaign retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterCampaignResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'
    put:
      summary: Update Newsletter Campaign
      operationId: updateNewsletterCampaign
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NewsletterCampaignUpdateInput'
      responses:
        '200':
          description: Newsletter campaign updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterSimpleSuccessResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'
    delete:
      summary: Delete Newsletter Campaign
      operationId: deleteNewsletterCampaign
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      responses:
        '200':
          description: Newsletter campaign deleted successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterSimpleSuccessResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/campaigns/{campaignUuid}/clone:
    post:
      summary: Clone Newsletter Campaign
      operationId: cloneNewsletterCampaign
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: campaignUuid
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '201':
          description: Campaign cloned successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterCampaignMutationResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/campaigns/{campaignUuid}/send:
    post:
      summary: Start Newsletter Campaign Send
      operationId: sendNewsletterCampaign
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: campaignUuid
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Campaign send kickoff accepted (queueing starts asynchronously)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterActionResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Campaign is already sending or currently queueing
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/campaigns/{campaignUuid}/send-remaining:
    post:
      summary: Send Remaining Newsletter Recipients
      operationId: sendRemainingNewsletterCampaignRecipients
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: campaignUuid
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Remaining eligible recipients queued successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterActionResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/campaigns/{campaignUuid}/schedule:
    post:
      summary: Schedule Newsletter Campaign
      operationId: scheduleNewsletterCampaign
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: campaignUuid
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NewsletterScheduleInput'
      responses:
        '200':
          description: Campaign scheduled successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterSimpleSuccessResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/campaigns/{campaignUuid}/pause:
    post:
      summary: Pause Newsletter Campaign
      operationId: pauseNewsletterCampaign
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: campaignUuid
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Campaign paused successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterSimpleSuccessResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/campaigns/{campaignUuid}/cancel:
    post:
      summary: Cancel Newsletter Campaign
      operationId: cancelNewsletterCampaign
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: campaignUuid
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Campaign cancelled successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterSimpleSuccessResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/campaigns/{campaignUuid}/runs:
    get:
      summary: List Newsletter Campaign Runs
      operationId: listNewsletterCampaignRuns
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: campaignUuid
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: page
          in: query
          schema:
            type: integer
            minimum: 1
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
      responses:
        '200':
          description: Campaign runs retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterCampaignRunsResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/campaigns/{campaignUuid}/runs/{runUuid}:
    get:
      summary: Get Newsletter Campaign Run
      operationId: getNewsletterCampaignRun
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: campaignUuid
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: runUuid
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Campaign run retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterCampaignRunResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/groups:
    get:
      summary: List Customer Groups
      operationId: listNewsletterGroups
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      responses:
        '200':
          description: Customer groups retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterGroupListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'
    post:
      summary: Create Customer Group
      operationId: createNewsletterGroup
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NewsletterGroupCreateInput'
      responses:
        '201':
          description: Customer group created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterGroupMutationResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/groups/preview:
    post:
      summary: Preview Customer Group Rules
      operationId: previewNewsletterGroupRules
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - rules
              properties:
                rules:
                  $ref: '#/components/schemas/NewsletterSegmentRules'
      responses:
        '200':
          description: Customer group preview retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterGroupPreviewResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/products:
    get:
      summary: Search Newsletter Products
      operationId: searchNewsletterProducts
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: query
          in: query
          schema:
            type: string
          description: Search product name, SKU, or external product id.
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 50
            default: 10
      responses:
        '200':
          description: Products retrieved from existing orders
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterProductSearchResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/groups/{groupUuid}:
    parameters:
      - name: groupUuid
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      summary: Get Customer Group
      operationId: getNewsletterGroup
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      responses:
        '200':
          description: Customer group retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterGroupResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'
    put:
      summary: Update Customer Group
      operationId: updateNewsletterGroup
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NewsletterGroupUpdateInput'
      responses:
        '200':
          description: Customer group updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterSimpleSuccessResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'
    delete:
      summary: Delete Customer Group
      operationId: deleteNewsletterGroup
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      responses:
        '200':
          description: Customer group deleted successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterSimpleSuccessResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/groups/{groupUuid}/refresh:
    post:
      summary: Refresh Customer Group Membership
      operationId: refreshNewsletterGroup
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: groupUuid
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Customer group refreshed successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterActionResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/groups/{groupUuid}/customers:
    get:
      summary: List Group Customers
      operationId: listNewsletterGroupCustomers
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: groupUuid
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: page
          in: query
          schema:
            type: integer
            minimum: 1
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - name: query
          in: query
          schema:
            type: string
          description: Search contacts by email or name.
        - name: status
          in: query
          schema:
            type: string
            enum: [all, active, excluded, unsubscribed, invalid]
            default: all
      responses:
        '200':
          description: Group customers retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterGroupCustomersResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/customers:
    get:
      summary: Search Newsletter Customers
      operationId: searchNewsletterCustomers
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: query
          in: query
          schema:
            type: string
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 50
            default: 10
      responses:
        '200':
          description: Newsletter customers retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterCustomerSearchResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/templates:
    get:
      summary: List Newsletter Templates
      operationId: listNewsletterTemplates
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      responses:
        '200':
          description: Newsletter templates retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterTemplateListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'
    post:
      summary: Create Newsletter Template
      operationId: createNewsletterTemplate
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NewsletterTemplateInput'
      responses:
        '201':
          description: Newsletter template created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterTemplateCreateResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/templates/{templateUuid}:
    parameters:
      - name: templateUuid
        in: path
        required: true
        schema:
          type: string
          format: uuid
    put:
      summary: Update Newsletter Template
      operationId: updateNewsletterTemplate
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NewsletterTemplateInput'
      responses:
        '200':
          description: Newsletter template updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterSimpleSuccessResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'
    delete:
      summary: Delete Newsletter Template
      operationId: deleteNewsletterTemplate
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      responses:
        '200':
          description: Newsletter template deleted successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterSimpleSuccessResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/send/preview:
    post:
      summary: Render Newsletter Preview
      operationId: previewNewsletterSend
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NewsletterSendPreviewInput'
      responses:
        '200':
          description: Newsletter preview rendered successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterFreeformResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/send/test:
    post:
      summary: Send Newsletter Test Email
      operationId: sendNewsletterTest
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NewsletterSendTestInput'
      responses:
        '200':
          description: Newsletter test send completed successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterFreeformResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/send/queue/status:
    get:
      summary: Get Newsletter Queue Status
      operationId: getNewsletterQueueStatus
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: campaign_uuid
          in: query
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Queue status retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterFreeformResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/send/retry:
    post:
      summary: Retry Failed Newsletter Sends
      operationId: retryNewsletterSends
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - campaign_uuid
              properties:
                campaign_uuid:
                  type: string
                  format: uuid
      responses:
        '200':
          description: Failed sends re-queued successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterActionResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/send/limits:
    get:
      summary: Get Newsletter Send Limits
      operationId: getNewsletterSendLimits
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      responses:
        '200':
          description: Newsletter send limits retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterFreeformResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/analytics/overview:
    get:
      summary: Get Newsletter Analytics Overview
      operationId: getNewsletterAnalyticsOverview
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      responses:
        '200':
          description: Newsletter analytics overview retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterFreeformResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/analytics/{campaignUuid}:
    get:
      summary: Get Newsletter Campaign Analytics
      operationId: getNewsletterCampaignAnalytics
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: campaignUuid
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Campaign analytics retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterFreeformResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/analytics/{campaignUuid}/opens:
    get:
      summary: List Newsletter Open Events
      operationId: listNewsletterOpenEvents
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: campaignUuid
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Open events retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterFreeformResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/analytics/{campaignUuid}/clicks:
    get:
      summary: List Newsletter Click Events
      operationId: listNewsletterClickEvents
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: campaignUuid
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Click events retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterFreeformResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/analytics/{campaignUuid}/recipients:
    get:
      summary: List Newsletter Campaign Recipients
      operationId: listNewsletterCampaignRecipients
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: campaignUuid
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: type
          in: query
          schema:
            type: string
            enum: [received, not_received, opened, clicked]
        - name: page
          in: query
          schema:
            type: integer
            minimum: 1
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - name: q
          in: query
          schema:
            type: string
      responses:
        '200':
          description: Campaign recipients retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterFreeformResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/unsubscribes:
    get:
      summary: List Newsletter Unsubscribes
      operationId: listNewsletterUnsubscribes
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            minimum: 1
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
      responses:
        '200':
          description: Newsletter unsubscribes retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterFreeformResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/unsubscribes/manual:
    post:
      summary: Manually Unsubscribe Newsletter Contact
      operationId: manuallyUnsubscribeNewsletterContact
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - email
              properties:
                email:
                  type: string
                  format: email
      responses:
        '200':
          description: Contact manually unsubscribed successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterSimpleSuccessResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/unsubscribes/resubscribe:
    post:
      summary: Resubscribe Newsletter Contact By Email
      operationId: resubscribeNewsletterContactByEmail
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - email
              properties:
                email:
                  type: string
                  format: email
      responses:
        '200':
          description: Contact resubscribed successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterSimpleSuccessResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/unsubscribes/check/{email}:
    get:
      summary: Check Newsletter Unsubscribe Status
      operationId: checkNewsletterUnsubscribeStatus
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: email
          in: path
          required: true
          schema:
            type: string
            format: email
      responses:
        '200':
          description: Unsubscribe status retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterFreeformResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/public/v1/newsletters/unsubscribes/{unsubscribeUuid}/resubscribe:
    post:
      summary: Resubscribe Newsletter Contact
      operationId: resubscribeNewsletterContact
      tags:
        - Newsletters
      security:
        - bearerAuth: []
        - apiKeyHeader: []
      parameters:
        - name: unsubscribeUuid
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Contact resubscribed successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsletterSimpleSuccessResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API_KEY:API_SECRET
      description: |
        API Key and Secret in the format: `API_KEY:API_SECRET`
        
        Example: `Authorization: Bearer sk_abc123:ss_xyz789`
    apiKeyHeader:
      type: apiKey
      in: header
      name: X-API-Key
      description: |
        API Key provided in custom header. Must be used together with X-API-Secret header.
        
        Example:
        ```
        X-API-Key: sk_abc123
        X-API-Secret: ss_xyz789
        ```
        
  schemas:
    OrderInput:
      type: object
      required:
        - order_number
        - customer_name
        - total_amount
      properties:
        order_number:
          type: string
          description: Unique order number from your store
          example: "12345"
        customer_name:
          type: string
          description: Full name of the customer
          example: "John Doe"
        customer_email:
          type: string
          format: email
          description: Customer email address
          example: "john.doe@example.com"
        customer_phone:
          type: string
          description: Customer phone number
          example: "+30 210 1234567"
        total_amount:
          type: number
          format: float
          description: Total order amount including taxes and shipping
          example: 150.50
        subtotal:
          type: number
          format: float
          description: Subtotal before taxes and shipping
          example: 130.00
        tax_amount:
          type: number
          format: float
          description: Total tax amount
          example: 15.50
        shipping_amount:
          type: number
          format: float
          description: Shipping cost
          example: 5.00
        discount_amount:
          type: number
          format: float
          description: Discount amount applied
          example: 0.00
        currency:
          type: string
          description: Currency code (ISO 4217)
          example: "EUR"
          default: "EUR"
        status:
          type: string
          description: |
            Order status (free-form string, max 255 chars).
            Recommended values: pending, processing, completed, cancelled, refunded, failed.
          maxLength: 255
          example: "processing"
        payment_method:
          type: string
          description: Payment method used
          example: "Credit Card"
        payment_status:
          type: string
          description: |
            Payment status (free-form string, max 255 chars).
            Recommended values: pending, paid, failed, refunded.
          maxLength: 255
          example: "paid"
        shipping_method:
          type: string
          description: Shipping method selected
          example: "Standard Shipping"
        tracking_number:
          type: string
          description: Shipping tracking number
          example: "TRACK123456"
        courier:
          type: string
          description: |
            Courier/provider identifier for imported shipment vouchers.
            When provided with `voucher` or `tracking_url`, voucher data is stored in the `vouchers` table.
          example: "acs"
        tracking_url:
          type: string
          format: uri
          description: Public tracking URL for this shipment
          example: "https://tracking.example.com/TRACK123456"
        voucher:
          $ref: '#/components/schemas/ExternalVoucherInput'
        tags:
          type: array
          description: Optional order tags to create/reuse and attach to the order
          items:
            $ref: '#/components/schemas/TagInput'
        order_date:
          type: string
          format: date
          description: |
            Date when order was placed (YYYY-MM-DD).
            Current runtime behavior on OpenCart, WooCommerce, and custom create/batch insert paths also accepts:
            - `2025-11-13 16:30:00`
            - `2025-11-13T16:30:00`
            - `2025-11-13T16:30:00+00:00`
            - `2025-11-13T16:30:00+02:00` (normalized by MySQL to UTC before storage)
          example: "2025-11-13"
        shipped_at:
          type: string
          format: date-time
          nullable: true
          description: Timestamp when the order was shipped
          example: "2025-11-13T16:30:00Z"
        delivered_at:
          type: string
          format: date-time
          nullable: true
          description: Timestamp when the order was delivered
          example: "2025-11-14T12:10:00Z"
        notes:
          type: string
          description: Customer-facing notes
          example: "Customer requested gift wrapping"
        internal_notes:
          type: string
          description: Internal notes (not visible to customer)
          example: "Top customer - priority handling"
        shipping_address:
          $ref: '#/components/schemas/Address'
        billing_address:
          $ref: '#/components/schemas/Address'
        items:
          type: array
          description: Order line items
          items:
            $ref: '#/components/schemas/OrderItem'
        raw_data:
          type: object
          description: Raw order data from OpenCart/WooCommerce for reference
          properties:
            date_added:
              type: string
              format: date-time
            date_modified:
              type: string
              format: date-time
            order_id:
              type: string
            history:
              type: array
              items:
                type: object
                properties:
                  status:
                    type: string
                  comment:
                    type: string
                  date_added:
                    type: string
                    format: date-time
    
    OrderUpdateInput:
      type: object
      description: All fields are optional for updates
      properties:
        customer_name:
          type: string
        customer_email:
          type: string
          format: email
        customer_phone:
          type: string
        total_amount:
          type: number
          format: float
        status:
          type: string
          description: |
            Order status (free-form string, max 255 chars).
            Recommended values: pending, processing, completed, cancelled, refunded, failed.
          maxLength: 255
        payment_status:
          type: string
          description: |
            Payment status (free-form string, max 255 chars).
            Recommended values: pending, paid, failed, refunded.
          maxLength: 255
        tracking_number:
          type: string
          description: Shipping tracking number
        shipping_method:
          type: string
          description: Shipping method selected
        courier:
          type: string
          description: Courier/provider identifier for imported shipment vouchers
        tracking_url:
          type: string
          format: uri
          description: Public tracking URL for this shipment
        voucher:
          $ref: '#/components/schemas/ExternalVoucherInput'
        shipped_at:
          type: string
          format: date-time
          nullable: true
          description: Timestamp when the order was shipped
        delivered_at:
          type: string
          format: date-time
          nullable: true
          description: Timestamp when the order was delivered
        tags:
          type: array
          nullable: true
          description: |
            Optional order tags for this order.
            Pass `null` to clear all tags from the order.
          items:
            $ref: '#/components/schemas/TagInput'
        notes:
          type: string
        internal_notes:
          type: string

    ExternalVoucherInput:
      type: object
      description: |
        Optional voucher payload for imported orders.
        Data is stored in the existing `vouchers` table (no additional order columns required).
      properties:
        courier:
          type: string
          description: Courier/provider identifier (for example `acs`, `speedex`, `geniki`)
          example: "acs"
        tracking_number:
          type: string
          description: Tracking number assigned by courier
          example: "TRACK123456"
        tracking_url:
          type: string
          format: uri
          description: Public URL to track shipment progress
          example: "https://tracking.example.com/TRACK123456"
        voucher_number:
          type: string
          description: Provider-specific voucher/label number
          example: "ACS-V-12345"
        provider_data:
          type: object
          description: Additional provider-specific metadata stored under `shipping_provider_data`
          additionalProperties: true

    ExternalVoucher:
      type: object
      properties:
        uuid:
          type: string
          format: uuid
        order_uuid:
          type: string
          format: uuid
        tracking_number:
          type: string
          nullable: true
        shipping_provider:
          type: string
          example: "acs"
        tracking_url:
          type: string
          format: uri
          nullable: true
        voucher_url:
          type: string
          format: uri
          nullable: true
        voucher_number:
          type: string
          nullable: true
        provider_data:
          type: object
          additionalProperties: true
        created_at:
          type: string
          format: date-time
          nullable: true
        updated_at:
          type: string
          format: date-time
          nullable: true

    TagInput:
      type: object
      required:
        - label
      properties:
        label:
          type: string
          description: Tag label
          example: "priority"
        color:
          type: string
          nullable: true
          description: Optional tag color (for example `oklch(...)` or `var(--tag-color)`)
          example: "oklch(0.62 0.15 24)"

    Tag:
      type: object
      properties:
        uuid:
          type: string
          format: uuid
          example: "01934567-89ab-7cde-f012-3456789abcde"
        name:
          type: string
          minLength: 1
          example: "priority"
        slug:
          type: string
          example: "priority"
        color:
          type: string
          example: "oklch(0.62 0.15 24)"
    
    Order:
      type: object
      properties:
        uuid:
          type: string
          format: uuid
          description: Unique identifier for the order
          example: "01934567-89ab-7cde-f012-3456789abcde"
        store_uuid:
          type: string
          format: uuid
          description: Store UUID this order belongs to
        order_number:
          type: string
          example: "12345"
        customer_name:
          type: string
          example: "John Doe"
        customer_email:
          type: string
          example: "john.doe@example.com"
        customer_phone:
          type: string
          example: "+30 210 1234567"
        total_amount:
          type: number
          format: float
          example: 150.50
        subtotal:
          type: number
          format: float
          example: 130.00
        tax_amount:
          type: number
          format: float
          example: 15.50
        shipping_amount:
          type: number
          format: float
          example: 5.00
        discount_amount:
          type: number
          format: float
          example: 0.00
        currency:
          type: string
          example: "EUR"
        status:
          type: string
          example: "processing"
        payment_method:
          type: string
          example: "Credit Card"
        payment_status:
          type: string
          example: "paid"
        shipping_method:
          type: string
          example: "Standard Shipping"
        tracking_number:
          type: string
          example: "TRACK123456"
        order_date:
          type: string
          format: date
          example: "2025-11-13"
        shipped_at:
          type: string
          format: date-time
          nullable: true
          example: "2025-11-13T16:30:00Z"
        delivered_at:
          type: string
          format: date-time
          nullable: true
          example: "2025-11-14T12:10:00Z"
        notes:
          type: string
        internal_notes:
          type: string
        shipping_address:
          $ref: '#/components/schemas/Address'
        billing_address:
          $ref: '#/components/schemas/Address'
        items:
          type: array
          items:
            $ref: '#/components/schemas/OrderItem'
        tags:
          type: array
          items:
            $ref: '#/components/schemas/Tag'
        vouchers:
          type: array
          items:
            $ref: '#/components/schemas/ExternalVoucher'
        created_at:
          type: string
          format: date-time
          example: "2025-11-13T14:30:00Z"
        updated_at:
          type: string
          format: date-time
          example: "2025-11-13T15:45:00Z"
    
    Address:
      type: object
      properties:
        first_name:
          type: string
          example: "John"
        last_name:
          type: string
          example: "Doe"
        company:
          type: string
          example: "Acme Corp"
        address_1:
          type: string
          example: "123 Main Street"
        address_2:
          type: string
          example: "Apt 4B"
        city:
          type: string
          example: "Athens"
        postcode:
          type: string
          example: "10431"
        country:
          type: string
          example: "Greece"
        zone:
          type: string
          description: State/Province/Region
          example: "Attica"
    
    OrderItem:
      type: object
      required:
        - product_name
        - quantity
        - price
      properties:
        product_sku:
          type: string
          description: Product SKU/Code
          example: "PROD-001"
        product_name:
          type: string
          description: Product name
          example: "Wireless Mouse"
        product_id:
          type: string
          description: Product ID from your store
          example: "12345"
        quantity:
          type: integer
          minimum: 1
          description: Quantity ordered
          example: 2
        price:
          type: number
          format: float
          description: Unit price
          example: 25.00
        total_price:
          type: number
          format: float
          description: Total price (quantity × price)
          example: 50.00
        tax_amount:
          type: number
          format: float
          description: Tax amount for this item
          example: 5.00
        discount_amount:
          type: number
          format: float
          description: Discount amount for this item
          example: 0.00
        product_options:
          type: object
          description: Product options/variations (e.g., color, size)
          example:
            color: "Black"
            size: "Medium"
        product_metadata:
          type: object
          description: Additional product metadata
          example:
            weight: "100g"
            dimensions: "10x5x3cm"
    
    OrderResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        data:
          $ref: '#/components/schemas/Order'
        message:
          type: string
          example: "Order created successfully"
    
    BatchOrderResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        total_orders:
          type: integer
          description: Total number of orders in the batch
          example: 5
        successful:
          type: integer
          description: Number of successfully processed orders
          example: 4
        failed:
          type: integer
          description: Number of failed orders
          example: 1
        results:
          type: array
          items:
            type: object
            properties:
              order_number:
                type: string
                example: "12345"
              success:
                type: boolean
                example: true
              order_uuid:
                type: string
                format: uuid
                description: UUID of created/updated order (only if success=true)
                example: "01934567-89ab-7cde-f012-3456789abcde"
              message:
                type: string
                description: Success message (only if success=true)
                example: "Order created successfully"
              error:
                type: string
                description: Error message (only if success=false)
                example: "Order number already exists for this store"
        store:
          type: object
          properties:
            uuid:
              type: string
              format: uuid
            name:
              type: string
            type:
              type: string
              enum: [opencart, woocommerce]

    NewsletterCampaign:
      type: object
      properties:
        uuid:
          type: string
          format: uuid
        store_uuid:
          type: string
          format: uuid
        user_uuid:
          type: string
          format: uuid
        name:
          type: string
          minLength: 1
        subject:
          type: string
        preview_text:
          type: string
          nullable: true
        smtp_sender_id:
          type: string
          nullable: true
        status:
          type: string
          enum: [draft, scheduled, sending, sent, paused, cancelled]
        scheduled_at:
          type: string
          format: date-time
          nullable: true
        sent_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      additionalProperties: true

    NewsletterCampaignCreateInput:
      type: object
      required:
        - name
        - subject
      properties:
        name:
          type: string
        subject:
          type: string
        preview_text:
          type: string
        smtp_sender_id:
          type: string
          nullable: true
        content_html:
          type: string
        content_text:
          type: string
        customer_group_ids:
          type: array
          items:
            type: string
            format: uuid

    NewsletterCampaignUpdateInput:
      type: object
      properties:
        name:
          type: string
        subject:
          type: string
        preview_text:
          type: string
        smtp_sender_id:
          type: string
          nullable: true
        content_html:
          type: string
        content_text:
          type: string
        customer_group_ids:
          type: array
          items:
            type: string
            format: uuid

    NewsletterCampaignResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          $ref: '#/components/schemas/NewsletterCampaign'

    NewsletterCampaignListResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            campaigns:
              type: array
              items:
                $ref: '#/components/schemas/NewsletterCampaign'
        pagination:
          $ref: '#/components/schemas/Pagination'

    NewsletterCampaignMutationResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            uuid:
              type: string
              format: uuid
            name:
              type: string
            subject:
              type: string
            status:
              type: string
          additionalProperties: true

    NewsletterCampaignRun:
      type: object
      properties:
        run_uuid:
          type: string
          format: uuid
        started_at:
          type: string
          format: date-time
          nullable: true
        last_activity_at:
          type: string
          format: date-time
          nullable: true
        total:
          type: integer
        pending_count:
          type: integer
        processing_count:
          type: integer
        sent_count:
          type: integer
        failed_count:
          type: integer
        cancelled_count:
          type: integer
        retried_count:
          type: integer
        pending_retry_count:
          type: integer
        terminal_failed_count:
          type: integer
      additionalProperties: true

    NewsletterCampaignRunsResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            runs:
              type: array
              items:
                $ref: '#/components/schemas/NewsletterCampaignRun'
        pagination:
          $ref: '#/components/schemas/Pagination'

    NewsletterCampaignRunResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          additionalProperties: true

    NewsletterSegmentCondition:
      type: object
      required:
        - field
        - operator
      properties:
        field:
          type: string
          enum:
            - order_count
            - total_spent
            - avg_order_value
            - last_order_date
            - location
            - customer_type
            - order_status
            - registration_date
            - customer_email
            - customer_name
            - purchased_product
        operator:
          type: string
          enum:
            - equals
            - not_equals
            - greater_than
            - less_than
            - between
            - contains
            - in
            - not_in
        value:
          nullable: true

    NewsletterProductSuggestion:
      type: object
      properties:
        product_key:
          type: string
          description: Product identity key, chosen from product_id, product_sku, or product_name.
        match_type:
          type: string
          enum: [product_id, product_sku, product_name]
        product_id:
          type: string
          nullable: true
        product_sku:
          type: string
          nullable: true
        product_name:
          type: string
        default_unit_price:
          type: number
          nullable: true
        last_order_date:
          type: string
          format: date-time
          nullable: true
        used_count:
          type: integer

    NewsletterProductSearchResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            products:
              type: array
              items:
                $ref: '#/components/schemas/NewsletterProductSuggestion'

    NewsletterSegmentRules:
      type: object
      required:
        - conditions
        - operator
      properties:
        conditions:
          type: array
          items:
            $ref: '#/components/schemas/NewsletterSegmentCondition'
        operator:
          type: string
          enum: [AND, OR]

    NewsletterGroup:
      type: object
      properties:
        uuid:
          type: string
          format: uuid
        store_uuid:
          type: string
          format: uuid
        user_uuid:
          type: string
          format: uuid
        name:
          type: string
          minLength: 1
        description:
          type: string
          nullable: true
        rules:
          $ref: '#/components/schemas/NewsletterSegmentRules'
        customer_count_cached:
          type: integer
          description: Active emailable members currently cached for the group.
        unsubscribed_count_cached:
          type: integer
          description: Matching customers currently excluded because they are unsubscribed.
        customer_count_updated_at:
          type: string
          format: date-time
          nullable: true
        is_active:
          type: boolean
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      additionalProperties: true

    NewsletterGroupCreateInput:
      type: object
      required:
        - name
        - rules
      properties:
        name:
          type: string
          minLength: 1
        description:
          type: string
        rules:
          $ref: '#/components/schemas/NewsletterSegmentRules'

    NewsletterGroupUpdateInput:
      type: object
      properties:
        name:
          type: string
          minLength: 1
        description:
          type: string
        rules:
          $ref: '#/components/schemas/NewsletterSegmentRules'
        is_active:
          type: boolean

    NewsletterGroupResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          $ref: '#/components/schemas/NewsletterGroup'

    NewsletterGroupListResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            groups:
              type: array
              items:
                $ref: '#/components/schemas/NewsletterGroup'

    NewsletterGroupMutationResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            uuid:
              type: string
              format: uuid
            name:
              type: string
            customer_count:
              type: integer
            unsubscribed_count:
              type: integer
          additionalProperties: true

    NewsletterCustomer:
      type: object
      properties:
        customer_email:
          type: string
        customer_name:
          type: string
          nullable: true
        order_count:
          type: integer
        total_spent:
          type: number
          format: float
        average_order_value:
          type: number
          format: float
        first_order_date:
          type: string
          nullable: true
        last_order_date:
          type: string
          nullable: true
        status:
          type: string
          enum: [active, unsubscribed, invalid]
        exclusion_reason:
          type: string
          enum: [unsubscribed, invalid_email]
          nullable: true
      additionalProperties: true

    NewsletterGroupPreviewResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            count:
              type: integer
            unsubscribed_count:
              type: integer
            preview_customers:
              type: array
              items:
                $ref: '#/components/schemas/NewsletterCustomer'

    NewsletterGroupCustomersResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            customers:
              type: array
              items:
                $ref: '#/components/schemas/NewsletterCustomer'
            summary:
              type: object
              properties:
                total:
                  type: integer
                active:
                  type: integer
                excluded:
                  type: integer
                unsubscribed:
                  type: integer
                invalid:
                  type: integer
            pagination:
              $ref: '#/components/schemas/Pagination'

    NewsletterCustomerSearchResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            customers:
              type: array
              items:
                $ref: '#/components/schemas/NewsletterCustomer'

    NewsletterTemplate:
      type: object
      properties:
        uuid:
          type: string
          format: uuid
        store_uuid:
          type: string
          format: uuid
        user_uuid:
          type: string
          format: uuid
        name:
          type: string
        description:
          type: string
          nullable: true
        category:
          type: string
          nullable: true
        content_html:
          type: string
          nullable: true
        content_text:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    NewsletterTemplateInput:
      type: object
      required:
        - name
      properties:
        name:
          type: string
        description:
          type: string
        category:
          type: string
        content_html:
          type: string
        content_text:
          type: string

    NewsletterTemplateListResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            templates:
              type: array
              items:
                $ref: '#/components/schemas/NewsletterTemplate'

    NewsletterTemplateCreateResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            uuid:
              type: string
              format: uuid

    NewsletterScheduleInput:
      type: object
      required:
        - scheduled_at
      properties:
        scheduled_at:
          type: string
          format: date-time

    NewsletterSendPreviewInput:
      type: object
      properties:
        campaign_uuid:
          type: string
          format: uuid
        customer_email:
          type: string
          format: email
        customer_name:
          type: string
        variables:
          type: object
          additionalProperties: true

    NewsletterSendTestInput:
      type: object
      required:
        - campaign_uuid
        - email
      properties:
        campaign_uuid:
          type: string
          format: uuid
        email:
          type: string
          format: email
        customer_name:
          type: string

    NewsletterSimpleSuccessResponse:
      type: object
      properties:
        success:
          type: boolean

    NewsletterActionResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          additionalProperties: true
        message:
          type: string

    NewsletterFreeformResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          additionalProperties: true
        pagination:
          $ref: '#/components/schemas/Pagination'
      additionalProperties: true
    
    Pagination:
      type: object
      properties:
        page:
          type: integer
          example: 1
        limit:
          type: integer
          example: 20
        total:
          type: integer
          example: 150
        pages:
          type: integer
          example: 8
    
    Error:
      type: object
      properties:
        success:
          type: boolean
          example: false
        error:
          type: string
          example: "Invalid request"
        details:
          type: string
          description: Additional error details
          example: "Missing required field: order_number"
  
  responses:
    BadRequest:
      description: Bad request - Invalid input data
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            success: false
            error: "Validation failed"
            details: "Missing required field: order_number"
    
    Unauthorized:
      description: Unauthorized - Invalid or missing API credentials
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            success: false
            error: "Unauthorized"
            details: "Invalid API credentials"
    
    NotFound:
      description: Not found - Resource does not exist
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            success: false
            error: "Order not found"
            details: "No order found with UUID: 01934567-89ab-7cde-f012-3456789abcde"
    
    RateLimitExceeded:
      description: Rate limit exceeded - Too many requests
      headers:
        Retry-After:
          description: Number of seconds to wait before retrying
          schema:
            type: integer
            example: 45
        X-RateLimit-Limit:
          description: Maximum requests allowed for the current plan and operation type in the current window
          schema:
            type: integer
            example: 200
        X-RateLimit-Remaining:
          description: Remaining requests in current window
          schema:
            type: integer
            example: 0
        X-RateLimit-Reset:
          description: Time when the rate limit resets (ISO 8601)
          schema:
            type: string
            format: date-time
            example: "2025-12-10T15:30:45Z"
      content:
        application/json:
          schema:
            type: object
            properties:
              success:
                type: boolean
                example: false
              error:
                type: string
                example: "Rate limit exceeded"
              details:
                type: string
                example: "Maximum 200 requests per 60 seconds allowed for single operations. Please retry after 45 seconds."
              retryAfter:
                type: integer
                description: Seconds to wait before retrying
                example: 45
              limit:
                type: integer
                description: Applied rate limit threshold
                example: 200
              limitType:
                type: string
                description: Type of rate limit
                example: "single operations"
    
    InternalServerError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            success: false
            error: "Internal server error"
            details: "An unexpected error occurred. Please try again later."

tags:
  - name: OpenCart Orders
    description: Order synchronization endpoints for OpenCart stores
  - name: Custom Orders
    description: OpenCart-compatible order synchronization endpoints for custom integrations
  - name: WooCommerce Orders
    description: Order synchronization endpoints for WooCommerce stores
  - name: Newsletters
    description: |
      Store-scoped public newsletter management endpoints.

      Newsletter management endpoints are authenticated with the same API key/secret
      used for store integrations. Public newsletter clients do not send `X-Store-ID`;
      the store is resolved from the credentials. Templates are store-shared resources.
```
