> ## Documentation Index
> Fetch the complete documentation index at: https://docs.brand-wallet.com/llms.txt
> Use this file to discover all available pages before exploring further.

# The Transaction Object

> The shape of a BrandWallet transaction: the basket you submit and the executed loyalty actions you get back.

**A transaction represents a single basket for one customer card. You submit it to [POST /v1/transactions/process](/api/process-transaction) (or preview it with [validate](/api/validate-transaction)); BrandWallet responds with the loyalty actions it executed and the amount to charge.**

Request object:

```json theme={null}
{
  "customer": "3d8ec956-de2a-4d43-9c1e-74475b153362",   // required — customer id
  "card": "41dd8263-288e-4b55-bf2e-6a82995a0441",       // required — card id (not a campaign id)
  "user": "usr-ext-1042",                                // opaque id — defaults to the API user
  "branch": "brn-ext-07",                                // opaque id — defaults to the API user's branch
  "items": [
    {
      "id": "4221450",                                   // product opaque id
      "name": "Americano",
      "quantity": 4,
      "amount": 140,                                     // line unit amount — not payment math
      "productPrice": 150,                               // list price — menu sync only
      "categories": [
        { "id": "cat-31", "name": "Hot Drinks" }         // category opaque id
      ]
    }
  ],
  "amount": 550,                                         // register total: discounts deducted, points not
  "orderId": "ORD-2026-000123",                          // idempotency key
  "redeem": {
    "coupons": [
      { "id": "906550fd-…", "productOpaqueId": "4221450" } // coupon to redeem (+ the product it was used for)
    ],
    "points": 120                                        // points to spend
  }
}
```

Response object:

```json theme={null}
{
  "transactionId": "7f1b6c20-…",                           // use it to roll the transaction back
  "message": "You collected 4 stamps, earned a Free Coffee coupon. Amount to pay: 538",
  "executedActions": [
    {
      "actionName": "collect-stamps",
      "transactionCategory": "collect-stamps",
      "netCollected": 4,
      "previousCollectableValue": 3,
      "result": { "value": 2, "total": 12 },
      "rewards": ["2f19b6ba-…"],
      "milestones": []
    }
  ],
  "executedPromotions": [
    {
      "promotion": "b34466d6-…",
      "name": "Weekend double coupon",
      "actionType": "issue_coupon",
      "coupons": [{ "_id": "5d8a1c2e-…", "coupon": "9a417359-…", "code": "WKND-4821" }]
    }
  ],
  "amount": 550,
  "discountAmount": 0,
  "payableAmount": 538,
  "redeemed": {
    "couponIds": ["906550fd-…"],
    "autoAppliedCouponIds": [],
    "points": 120,
    "pointsValue": 12
  }
}
```

### Semantics

| Concept | Meaning |
| - | - |
| One payload = one basket | Construct and submit one transaction per customer interaction. |
| `card` drives the campaigns | The campaigns that run are the ones attached to the selected card (see [find](/api/find-customer) → `cards[].campaigns`). |
| Opaque ids | `items[].id`, `items[].categories[].id`, `user`, `branch`, and `orderId` are your external identifiers; BrandWallet resolves them internally. |
| `amount` is the money truth | The register total with your own discounts already deducted; BrandWallet-applied deductions (promotion discounts, points) not deducted. Those are applied server-side (`discountAmount`, `redeemed.pointsValue`); charge `payableAmount`. |
| `items[].amount` vs `productPrice` | `amount` is the line unit amount (campaign condition input); `productPrice` is the product's list price, used only by [menu sync](/api/process-transaction#menu-synchronization-lazy-sync). Neither enters payment math. |
| `redeem` spends | `redeem.coupons` are redeemed (a product coupon can carry `productOpaqueId` — recorded, and its quantity is excluded from earning when the line is in `items[]`) and `redeem.points` are deducted from the card's balance; fails with `insufficient_collectable_balance` when points exceed the balance. |
| `orderId` deduplicates | A completed transaction with the same `orderId` is rejected with `duplicate_transaction` and no side effects. |
| `executedActions` is the outcome | One entry per campaign action that ran: what was collected (`netCollected`), the balance before (`previousCollectableValue`) and after (`result`), any granted coupon ids (`rewards`), and triggered `milestones`. |
| `executedPromotions` lists applied promotions | One entry per promotion applied on top of the campaign (omitted when none), e.g. an `issue_coupon` promotion with the coupons it granted, or an `apply_discount` promotion with the order discount it applied (deducted from `payableAmount`, totaled in `discountAmount`). |
| `transactionId` identifies the transaction | Returned by `process` (not by `validate`), used to [look it up](/api/get-transaction), [list](/api/list-transactions) it, and [roll it back](/api/rollback-transaction). |

Full field tables: [Process a transaction](/api/process-transaction). Flow guide: [Transaction Payload](/api/standard-payload).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.