> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developers.brevo.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developers.brevo.com/_mcp/server.

# Set up a loyalty program

## Overview

This guide walks through creating and configuring a loyalty program via API — from creation through to publishing. You can also configure programs in the Brevo UI (**Loyalty > Programs**) and use the API exclusively for runtime operations.

---

## Authentication

All Loyalty API requests require your Brevo API key as a request header.

```bash
-H "api-key: YOUR_API_KEY"
-H "Content-Type: application/json"
```

Retrieve your API key from **Settings > API Keys** in the Brevo app, or at [app.brevo.com/settings/keys/api](https://app.brevo.com/settings/keys/api).

Never expose your API key in client-side code. All Loyalty API calls must be made server-side.

---

## Steps

### Create the program

**Endpoint:** `POST https://api.brevo.com/v3/loyalty/config/programs`

```bash
curl --request POST \
  --url https://api.brevo.com/v3/loyalty/config/programs \
  --header 'api-key: YOUR_API_KEY' \
  --header 'content-type: application/json' \
  --data '{
    "name": "VIP Club",
    "description": "Earn points on every purchase and unlock exclusive rewards."
  }'
```

**Request parameters**

| Parameter     | Type   | Required | Description                                     |
| ------------- | ------ | -------- | ----------------------------------------------- |
| `name`        | string | Yes      | Program name, max 128 characters                |
| `description` | string | No       | Customer-facing description, max 256 characters |
| `documentId`  | string | No       | Optional external document reference            |
| `meta`        | object | No       | Arbitrary key-value metadata                    |

**Response (200)**

```json
{
  "id": "27xxdd7a-af67-0020-ba65-19d60000a26e",
  "name": "VIP Club",
  "description": "Earn points on every purchase and unlock exclusive rewards.",
  "state": "inactive",
  "createdAt": "2025-03-01T10:00:00.000Z",
  "updatedAt": "2025-03-01T10:00:00.000Z"
}
```

Save the returned `id` — you will use it as `{pid}` (program ID) in all subsequent calls.

`state` starts as `inactive`. Members cannot be enrolled until the program is published in Step 4.

### Define a balance definition

**Endpoint:** `POST https://api.brevo.com/v3/loyalty/config/programs/{pid}/balance-definitions`

```bash
curl --request POST \
  --url https://api.brevo.com/v3/loyalty/config/programs/27xxdd7a-.../balance-definitions \
  --header 'api-key: YOUR_API_KEY' \
  --header 'content-type: application/json' \
  --data '{
    "name": "Purchase Points",
    "unit": "points",
    "expiryPolicy": {
      "type": "rolling",
      "duration": 12,
      "unit": "month"
    },
    "roundingPolicy": "round_half_up"
  }'
```

**Request parameters**

| Parameter                        | Type         | Required                   | Description                                                                                                                                                                                                                             |
| -------------------------------- | ------------ | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`                           | string       | Yes                        | Internal name for this balance type                                                                                                                                                                                                     |
| `unit`                           | string       | Yes                        | Display label shown to members (e.g. `"points"`, `"€"`)                                                                                                                                                                                 |
| `expiryPolicy.type`              | string       | No                         | `never`, `rolling` (from last activity), `fixed` (calendar date each year), or inactivity-based (after N months without purchase)                                                                                                       |
| `expiryPolicy.duration` + `unit` | int + string | For `rolling` / inactivity | Duration before expiry (e.g. `12`, `"month"`)                                                                                                                                                                                           |
| `roundingPolicy`                 | string       | No                         | `none` (maintain decimals), `round_half_up` (nearest), `floor` (always round down), or `ceiling` (always round up)                                                                                                                      |
| `maxBalance`                     | number       | No                         | Maximum balance cap. Credits are refused once this limit is reached — triggers a `balance_transaction_unauthorized` event.                                                                                                              |
| `minBalance`                     | number       | No                         | Minimum balance threshold. Falling below this value can trigger automations.                                                                                                                                                            |
| `maxCreditPerOperation`          | number       | No                         | Maximum amount that can be credited in a single transaction.                                                                                                                                                                            |
| `maxDebitPerOperation`           | number       | No                         | Maximum amount that can be debited in a single transaction.                                                                                                                                                                             |
| `creditFrequencyLimit`           | object       | No                         | Maximum number of credit operations allowed per period.                                                                                                                                                                                 |
| `debitFrequencyLimit`            | object       | No                         | Maximum number of debit operations allowed per period.                                                                                                                                                                                  |
| `meta`                           | object       | No                         | Additional metadata for the balance definition. Supports `isInternal` (boolean) to mark the definition as internal. Internal balance definitions are excluded from member-facing balance reads unless `includeInternal=true` is passed. |

### Configure tier groups (optional)

**Endpoint:** `POST https://api.brevo.com/v3/loyalty/config/programs/{pid}/tier-groups`

```bash
curl --request POST \
  --url https://api.brevo.com/v3/loyalty/config/programs/27xxdd7a-.../tier-groups \
  --header 'api-key: YOUR_API_KEY' \
  --header 'content-type: application/json' \
  --data '{
    "name": "VIP Levels",
    "balanceDefinitionId": "{balanceDefinitionId}",
    "upgradeEvaluationTrigger": "real_time",
    "downgradeEvaluationTrigger": "membership_anniversary",
    "tiers": [
      { "name": "Bronze", "threshold": 0 },
      { "name": "Silver", "threshold": 300 },
      { "name": "Gold",   "threshold": 500 }
    ]
  }'
```

**Tier group parameters**

| Parameter                    | Type          | Required | Description                                                                                            |
| ---------------------------- | ------------- | -------- | ------------------------------------------------------------------------------------------------------ |
| `name`                       | string        | Yes      | Internal name for the tier group                                                                       |
| `balanceDefinitionId`        | string (UUID) | Yes      | The balance definition this tier group is evaluated against                                            |
| `upgradeEvaluationTrigger`   | string        | Yes      | When a member is evaluated for a higher tier                                                           |
| `downgradeEvaluationTrigger` | string        | Yes      | When a member is evaluated for a lower tier                                                            |
| `tiers`                      | array         | Yes      | Ordered list of tiers. Each tier requires `name` and `threshold`.                                      |
| `meta`                       | object        | No       | Additional metadata for the tier group. Supports `isInternal` (boolean) to mark the group as internal. |

**Tier evaluation trigger options**

| Value                    | Description                                        | Recommended use                         |
| ------------------------ | -------------------------------------------------- | --------------------------------------- |
| `real_time`              | Tier re-evaluated on every completed transaction   | Upgrades — reward immediately           |
| `membership_anniversary` | Re-evaluated once a year on enrollment anniversary | Downgrades — avoid mid-year status loss |
| `tier_anniversary`       | Re-evaluated once a year on tier entry anniversary | When tier-year tracking matters         |

Set `upgradeEvaluationTrigger: real_time` and `downgradeEvaluationTrigger: membership_anniversary`. This upgrades members instantly while protecting them from losing status mid-year.

Tier thresholds within the same group must be unique — two tiers cannot share the same entry point. Any threshold change requires re-publishing the program.

### Publish the program

**Endpoint:** `POST https://api.brevo.com/v3/loyalty/config/programs/{pid}/publish`

```bash
curl --request POST \
  --url https://api.brevo.com/v3/loyalty/config/programs/27xxdd7a-.../publish \
  --header 'api-key: YOUR_API_KEY'
```

**Response (200):** Returns the updated program object with `"state": "active"`.

Any configuration changes made after publishing — to tiers, balance definitions, or rewards — require calling `/publish` again before they take effect for members.