YNS loyalty is a points-based rewards program with three building blocks: **settings** (how points are earned and redeemed), **tiers** (membership levels reached by lifetime points, each with its own earning multiplier), and **campaigns** (time-boxed bonus multipliers scoped to specific products, categories, or collections). Settings and socials live in the store's JSON config, while tiers and campaigns are first-class records.

## Get Loyalty Settings

```
GET /api/v1/loyalty/settings
```

Returns the store's loyalty config: points rate, redemption rules, and expiry. The `loyalty` field is `null` when the program has never been configured.

```bash
curl \
  -H "Authorization: Bearer your_api_key" \
  https://your-store.yns.store/api/v1/loyalty/settings
```

### Response (200)

```json
{
  "loyalty": {
    "pointsPerUnit": 1,
    "redemptionRate": 100,
    "minRedemption": 100,
    "maxRedeemPercent": 50,
    "pointsExpiryMonths": null
  }
}
```

---

## Update Loyalty Settings

```
PUT /api/v1/loyalty/settings
```

Replaces the loyalty config wholesale.

### Request Body

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `pointsPerUnit` | `number` | 1 | Points earned per currency unit spent (positive) |
| `redemptionRate` | `number` | 100 | Points required to redeem one currency unit (positive) |
| `minRedemption` | `number` | 100 | Minimum points a customer must hold to redeem (positive) |
| `maxRedeemPercent` | `number` | 50 | Maximum share of an order payable with points, 1-100 (positive) |
| `pointsExpiryMonths` | `number \| null` | `null` | Months until earned points expire (`null` = never) |

```bash
curl -X PUT \
  -H "Authorization: Bearer your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"pointsPerUnit": 1, "redemptionRate": 100, "minRedemption": 100, "maxRedeemPercent": 50, "pointsExpiryMonths": 12}' \
  https://your-store.yns.store/api/v1/loyalty/settings
```

### Response (200)

```json
{
  "loyalty": {
    "pointsPerUnit": 1,
    "redemptionRate": 100,
    "minRedemption": 100,
    "maxRedeemPercent": 50,
    "pointsExpiryMonths": 12
  }
}
```

---

## List Loyalty Tiers

```
GET /api/v1/loyalty/tiers
```

Returns loyalty tiers. Members reach a tier once their lifetime points exceed its `threshold`.

### Query Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `limit` | `number` | 50 | Tiers per page (1-100) |
| `offset` | `number` | 0 | Tiers to skip |

```bash
curl \
  -H "Authorization: Bearer your_api_key" \
  https://your-store.yns.store/api/v1/loyalty/tiers
```

### Response (200)

```json
{
  "data": [
    {
      "id": "0191abc0-1234-7def-8000-000000000001",
      "name": "Gold",
      "threshold": 1000,
      "multiplier": 1.5,
      "color": "#FFD700",
      "sortOrder": 2,
      "createdAt": "2024-06-15T10:30:00.000Z",
      "updatedAt": "2024-06-15T10:30:00.000Z"
    }
  ],
  "meta": {
    "count": 1
  }
}
```

---

## Create Loyalty Tier

```
POST /api/v1/loyalty/tiers
```

Creates a tier. Members reach it once their lifetime points exceed the `threshold`, after which they earn points at the tier's `multiplier`.

### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | `string` | Yes | Tier name, e.g. `Gold` |
| `threshold` | `number` | Yes | Lifetime points required to reach this tier (integer ≥ 0) |
| `multiplier` | `number` | Yes | Points-earning multiplier for members in this tier (positive, e.g. `1.5`) |
| `color` | `string` | Yes | Badge color hex, e.g. `#FFD700` (must match `#RRGGBB`) |
| `sortOrder` | `number` | No | Display order (integer ≥ 0, default: `0`) |

```bash
curl -X POST \
  -H "Authorization: Bearer your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"name": "Gold", "threshold": 1000, "multiplier": 1.5, "color": "#FFD700", "sortOrder": 2}' \
  https://your-store.yns.store/api/v1/loyalty/tiers
```

### Response (201)

```json
{
  "id": "0191abc0-1234-7def-8000-000000000001",
  "name": "Gold",
  "threshold": 1000,
  "multiplier": 1.5,
  "color": "#FFD700",
  "sortOrder": 2,
  "createdAt": "2024-06-15T10:30:00.000Z",
  "updatedAt": "2024-06-15T10:30:00.000Z"
}
```

---

## Get Loyalty Tier

```
GET /api/v1/loyalty/tiers/{id}
```

Returns a single loyalty tier by UUID. Returns `404` if no tier matches.

---

## Update Loyalty Tier

```
PATCH /api/v1/loyalty/tiers/{id}
```

Partially updates a loyalty tier. Send only the fields you want to change – all fields accept the same types and constraints as [Create Loyalty Tier](#create-loyalty-tier).

```bash
curl -X PATCH \
  -H "Authorization: Bearer your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"threshold": 1500}' \
  https://your-store.yns.store/api/v1/loyalty/tiers/0191abc0-1234-7def-8000-000000000001
```

### Response (200)

Returns the updated tier.

---

## Delete Loyalty Tier

```
DELETE /api/v1/loyalty/tiers/{id}
```

Deletes a loyalty tier by UUID. At least one tier must always remain – deleting the last tier returns `409`.

```bash
curl -X DELETE \
  -H "Authorization: Bearer your_api_key" \
  https://your-store.yns.store/api/v1/loyalty/tiers/0191abc0-1234-7def-8000-000000000001
```

### Response (200)

```json
{
  "ok": true,
  "deleted": 1
}
```

### Last Tier (409)

```json
{
  "error": "Cannot delete the last remaining tier"
}
```

---

## List Loyalty Campaigns

```
GET /api/v1/loyalty/campaigns
```

Returns loyalty bonus campaigns – time-boxed multipliers that boost points earned on specific products, categories, or collections.

### Query Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `limit` | `number` | 50 | Campaigns per page (1-100) |
| `offset` | `number` | 0 | Campaigns to skip |
| `activeOnly` | `boolean` | – | Return only active campaigns |

```bash
curl \
  -H "Authorization: Bearer your_api_key" \
  "https://your-store.yns.store/api/v1/loyalty/campaigns?activeOnly=true"
```

### Response (200)

```json
{
  "data": [
    {
      "id": "0191abc0-1234-7def-8000-000000000010",
      "name": "Double Points Weekend",
      "earnMultiplier": 2,
      "scope": "ALL_PRODUCTS",
      "scopeIds": [],
      "startsAt": "2024-06-15T00:00:00.000Z",
      "endsAt": "2024-06-17T00:00:00.000Z",
      "active": true,
      "createdAt": "2024-06-14T09:00:00.000Z",
      "updatedAt": "2024-06-14T09:00:00.000Z"
    }
  ],
  "meta": {
    "count": 1
  }
}
```

---

## Create Loyalty Campaign

```
POST /api/v1/loyalty/campaigns
```

Creates a bonus points-earning campaign. `earnMultiplier` boosts points earned, `scope` (with `scopeIds`) targets what it applies to, and `startsAt`/`endsAt` bound the active window.

### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | `string` | Yes | Campaign name |
| `earnMultiplier` | `number` | Yes | Points-earning multiplier during the campaign (positive, e.g. `2` = 2×) |
| `scope` | `string` | Yes | One of `ALL_PRODUCTS`, `PRODUCT_IDS`, `CATEGORY_IDS`, `COLLECTION_IDS` |
| `scopeIds` | `string[]` | Conditional | Product/category/collection UUIDs – required unless `scope` is `ALL_PRODUCTS` |
| `startsAt` | `string \| null` | No | ISO 8601 start (`null` = active immediately) |
| `endsAt` | `string \| null` | No | ISO 8601 end (`null` = no end). Must be after `startsAt` |
| `active` | `boolean` | No | Whether the campaign is active (default: `true`) |

```bash
curl -X POST \
  -H "Authorization: Bearer your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"name": "Double Points Weekend", "earnMultiplier": 2, "scope": "ALL_PRODUCTS", "startsAt": "2024-06-15T00:00:00.000Z", "endsAt": "2024-06-17T00:00:00.000Z"}' \
  https://your-store.yns.store/api/v1/loyalty/campaigns
```

```bash
# Scoped to specific collections
curl -X POST \
  -H "Authorization: Bearer your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"name": "3× on New Arrivals", "earnMultiplier": 3, "scope": "COLLECTION_IDS", "scopeIds": ["0191abc0-0000-7000-8000-000000000200"]}' \
  https://your-store.yns.store/api/v1/loyalty/campaigns
```

### Response (201)

```json
{
  "id": "0191abc0-1234-7def-8000-000000000010",
  "name": "Double Points Weekend",
  "earnMultiplier": 2,
  "scope": "ALL_PRODUCTS",
  "scopeIds": [],
  "startsAt": "2024-06-15T00:00:00.000Z",
  "endsAt": "2024-06-17T00:00:00.000Z",
  "active": true,
  "createdAt": "2024-06-14T09:00:00.000Z",
  "updatedAt": "2024-06-14T09:00:00.000Z"
}
```

---

## Get Loyalty Campaign

```
GET /api/v1/loyalty/campaigns/{id}
```

Returns a single loyalty campaign by UUID. Returns `404` if no campaign matches.

---

## Update Loyalty Campaign

```
PATCH /api/v1/loyalty/campaigns/{id}
```

Partially updates a loyalty campaign. Fields accept the same types and constraints as [Create Loyalty Campaign](#create-loyalty-campaign). The same cross-field guards apply: `scopeIds` is required when changing `scope` to anything other than `ALL_PRODUCTS`, and `endsAt` must be after `startsAt`.

```bash
curl -X PATCH \
  -H "Authorization: Bearer your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"active": false}' \
  https://your-store.yns.store/api/v1/loyalty/campaigns/0191abc0-1234-7def-8000-000000000010
```

### Response (200)

Returns the updated campaign.

---

## Delete Loyalty Campaign

```
DELETE /api/v1/loyalty/campaigns/{id}
```

Deletes a loyalty campaign by UUID.

```bash
curl -X DELETE \
  -H "Authorization: Bearer your_api_key" \
  https://your-store.yns.store/api/v1/loyalty/campaigns/0191abc0-1234-7def-8000-000000000010
```

### Response (200)

```json
{
  "ok": true,
  "deleted": 1
}
```