> 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.

# Credit & debit points

## Overview

Every credit or debit in the Loyalty API goes through a **two-phase lifecycle**:

1. **Create** — the transaction is recorded with status `pending`. The member's balance is not yet updated.
2. **Complete** or **Cancel** — you explicitly confirm or void the operation.

This pattern mirrors real-world eCommerce flows: credit points only when a purchase is *confirmed*, not when it is merely *placed* — and cancel the credit if the order is cancelled or returned.

```
Step 1: Create transaction / balance order  →  status: PENDING   (balance unchanged)
Step 2a: Complete                           →  status: COMPLETED  (balance updated)
Step 2b: Cancel                             →  status: CANCELLED  (balance unchanged)
```

---

## Create a transaction

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

Use this for non-purchase events: sign-up bonuses, birthday credits, manual adjustments, referral rewards.

```bash
curl --request POST \
  --url https://api.brevo.com/v3/loyalty/balance/programs/27xxdd7a-.../transactions \
  --header 'api-key: YOUR_API_KEY' \
  --header 'content-type: application/json' \
  --data '{
    "contactId": 12345,
    "balanceDefinitionId": "a74cxx1d-4a96-4xx3-804e-dc3xxd9axxeb",
    "amount": 50,
    "autoComplete": false,
    "meta": {
      "reason": "signup_bonus"
    }
  }'
```

**Request parameters**

| Parameter               | Type    | Required | Description                                                                                                                     |
| ----------------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `contactId`             | integer | Yes\*    | Brevo contact ID (\*required unless `loyaltySubscriptionId` is provided)                                                        |
| `loyaltySubscriptionId` | string  | Yes\*    | Your external member ID (\*required unless `contactId` is provided)                                                             |
| `balanceDefinitionId`   | string  | Yes      | UUID of the balance definition to credit or debit                                                                               |
| `amount`                | double  | Yes      | Transaction amount. Positive = credit; negative = debit. Must be non-zero.                                                      |
| `autoComplete`          | boolean | No       | If `true`, the transaction is immediately completed. If `false` (default), it stays `pending` until you call `/complete`.       |
| `eventTime`             | string  | No       | ISO 8601 timestamp of when the triggering event occurred (for backdated imports)                                                |
| `expiryBalanceMinutes`  | integer | No       | Time in minutes before the credited balance expires. Must be > 0 if provided.                                                   |
| `ttl`                   | integer | No       | Time-to-live for the pending transaction in minutes. Auto-voided if not completed or cancelled within this window. Must be > 0. |
| `meta`                  | object  | No       | Arbitrary key-value metadata (e.g. `reason`, `campaign_id`)                                                                     |

**Response (200)**

```json
{
  "id": "txn_aaa111-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "amount": 50,
  "balanceDefinitionId": "a74cxx1d-4a96-4xx3-804e-dc3xxd9axxeb",
  "contactId": 12345,
  "loyaltyProgramId": "27xxdd7a-af67-0020-ba65-19d60000a26e",
  "status": "pending",
  "createdAt": "2025-03-01T10:00:00.000Z",
  "updatedAt": "2025-03-01T10:00:00.000Z",
  "cancelledAt": null,
  "completedAt": null,
  "rejectReason": null,
  "rejectedAt": null
}
```

Always persist the `id` returned by Create transaction. You need it to call `/complete` or `/cancel`. If you lose it, you will need to query the transaction list to retrieve it.

---

## Complete a transaction

**Endpoint:** `POST https://api.brevo.com/v3/loyalty/balance/programs/{pid}/transactions/{tid}/complete`

Completing a transaction finalizes the credit or debit: the member's balance is updated, tiers are re-evaluated, and reward rules are checked.

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

**Path parameters**

| Parameter | Type          | Required | Description                                              |
| --------- | ------------- | -------- | -------------------------------------------------------- |
| `pid`     | string (UUID) | Yes      | Loyalty program ID                                       |
| `tid`     | string (UUID) | Yes      | Transaction ID returned from the Create transaction call |

**Response (200)**

```json
{
  "id": "txn_aaa111-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "amount": 50,
  "balanceDefinitionId": "a74cxx1d-4a96-4xx3-804e-dc3xxd9axxeb",
  "contactId": 12345,
  "loyaltyProgramId": "27xxdd7a-af67-0020-ba65-19d60000a26e",
  "status": "completed",
  "completedAt": "2025-03-01T10:05:00.000Z",
  "cancelledAt": null,
  "rejectReason": null,
  "rejectedAt": null
}
```

**What happens after completion:**

* Member's balance is updated (e.g. 0 → 50 pts)
* If tier trigger is `real_time`: tier thresholds are evaluated and the member may be upgraded
* Webhook `balance_value_updated` is fired
* If a tier threshold is crossed: webhook `tier_association_updated` is fired
* If a reward rule matches: a voucher is attributed

**When to call Complete:**

| Scenario                           | Action                                                                |
| ---------------------------------- | --------------------------------------------------------------------- |
| Sign-up bonus — credit immediately | Create with `autoComplete: true`, or create then complete immediately |
| Purchase order confirmed / shipped | Complete the pending transaction                                      |
| Manual point adjustment (admin)    | Create then complete immediately                                      |

---

## Cancel a transaction

**Endpoint:** `POST https://api.brevo.com/v3/loyalty/balance/programs/{pid}/transactions/{tid}/cancel`

Cancelling a transaction voids it entirely. The member's balance is not affected.

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

**Response (200)**

```json
{
  "id": "txn_aaa111-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "amount": 50,
  "status": "cancelled",
  "cancelledAt": "2025-03-01T10:05:00.000Z",
  "completedAt": null,
  "rejectReason": null
}
```

**When to call Cancel:**

| Scenario                                 | Action                                |
| ---------------------------------------- | ------------------------------------- |
| Purchase order cancelled before shipment | Cancel the pending transaction        |
| Duplicate transaction detected           | Cancel the duplicate                  |
| Fraud or abuse detected                  | Cancel the pending transaction        |
| `ttl` is too short for your flow         | Cancel and recreate with a longer TTL |

**Handling returns after delivery (balance already completed):**

A completed transaction cannot be cancelled. Instead, create a new transaction with a negative `amount` (debit) and complete it.

```bash
# Step 1: create debit for returned order
--data '{
  "contactId": 12345,
  "balanceDefinitionId": "...",
  "amount": -100,
  "meta": { "reason": "return_order_789" }
}'

# Step 2: complete the debit
POST .../transactions/{debit_tid}/complete
```

---

## Create a balance order

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

A **balance order** is the right tool for crediting points on a purchase. It creates a single tracked event tied to a business order, which you later complete or cancel as one unit.

```bash
curl --request POST \
  --url https://api.brevo.com/v3/loyalty/balance/programs/27xxdd7a-.../create-order \
  --header 'api-key: YOUR_API_KEY' \
  --header 'content-type: application/json' \
  --data '{
    "contactId": 12345,
    "balanceDefinitionId": "a74cxx1d-4a96-4xx3-804e-dc3xxd9axxeb",
    "amount": 100,
    "source": "user",
    "dueAt": "2025-03-02T10:00:00.000Z",
    "meta": {
      "orderId": "order_789",
      "orderAmount": 89.99
    }
  }'
```

**Request parameters**

| Parameter             | Type          | Required | Description                                                        |
| --------------------- | ------------- | -------- | ------------------------------------------------------------------ |
| `contactId`           | integer       | Yes      | Brevo contact ID (must be ≥ 1)                                     |
| `balanceDefinitionId` | string (UUID) | Yes      | Which balance to credit                                            |
| `amount`              | double        | Yes      | Points or cashback amount to credit. Must be non-zero.             |
| `source`              | string        | Yes      | `"engine"` (system-triggered) or `"user"` (customer-facing action) |
| `dueAt`               | string        | Yes      | RFC 3339 timestamp: when this order is due to be processed         |
| `expiresAt`           | string        | No       | Optional expiry for the order itself (RFC 3339)                    |
| `meta`                | object        | No       | Store your order ID, cart total, or any custom metadata            |

**Response (200)**

```json
{
  "id": "ord_xyz789-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "transactionid": "txn_bbb222-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "contactId": 12345,
  "loyaltyProgramId": "27xxdd7a-af67-0020-ba65-19d60000a26e",
  "balanceDefinitionId": "a74cxx1d-4a96-4xx3-804e-dc3xxd9axxeb",
  "amount": 100,
  "source": "user",
  "dueAt": "2025-03-02T10:00:00.000Z",
  "processedAt": null,
  "createdAt": "2025-03-01T10:00:00.000Z",
  "updatedAt": "2025-03-01T10:00:00.000Z"
}
```

The order returns a `transactionid`. Use this to call `/complete` or `/cancel` when the purchase status is resolved.

**Transaction vs. balance order:** A direct transaction is for a single credit or debit event on a single balance. A balance order is intended for purchase flows where you want to track the originating business event (the order) independently from the balance operation.

---

## Which endpoint to use?

| Scenario                                        | Recommended endpoint                                     |
| ----------------------------------------------- | -------------------------------------------------------- |
| Sign-up bonus, birthday credit, referral reward | Create transaction with `autoComplete: true`             |
| Purchase — credit on confirmation               | Create balance order → Complete on confirmation          |
| Purchase — customer cancelled before shipping   | Create balance order → Cancel                            |
| Item returned after delivery                    | Create transaction (negative amount) → Complete          |
| Manual admin adjustment                         | Create transaction → Complete immediately                |
| Bulk import of historical points                | Create transaction with `eventTime` backdated → Complete |

---

## Error handling

| HTTP code | Meaning                          | What to do                                                                                                                 |
| --------- | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `401`     | Invalid or missing API key       | Check your `api-key` header                                                                                                |
| `403`     | Insufficient permissions         | Ensure the Loyalty module is active                                                                                        |
| `404`     | Program or transaction not found | Verify `{pid}` and `{tid}` path parameters                                                                                 |
| `422`     | Unprocessable entity             | Common causes: `amount` is 0; `contactId` doesn't exist; transaction already completed or cancelled; program not published |
| `500`     | Internal server error            | Retry with exponential backoff. If persistent, contact Brevo support.                                                      |