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

# Basket Lifecycle & Troubleshooting

This guide explains how the Thanx basket lifecycle works end-to-end and how to diagnose the most common integration problems. It complements the [Create & Update Basket](/loyalty/create-update-basket) endpoint reference with the behavioral context that most integrations need to pass certification on the first attempt.

**Who this is for:**

* Partners integrating the Loyalty API directly (POS, kiosk, online ordering)
* Anyone troubleshooting missing points, \$0 discounts, or unexplained basket behavior

**What you'll learn:**

* How a basket moves through its states and what the API does at each transition
* Which fields drive points accrual, and when they must be sent
* The three most common integration mistakes and how to fix them
* How to debug a basket that returned 201 but produced no purchase

## The Basket State Machine

A basket is an object partners manage across several API calls. Each call updates the basket's state, and points are only accrued at a specific transition. For the full state diagram, see the [Create & Update Basket](/loyalty/create-update-basket) endpoint reference.

| State       | When to send                                                    | What Thanx does                                                                                                                                                                                   | Required?   |
| ----------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
| `checkout`  | Any time the user modifies the basket or rewards pre-submission | Validates reward eligibility, returns updated discount amounts                                                                                                                                    | Required    |
| `placed`    | Order has been submitted; user can no longer modify             | Locks the reward; if using a points product, deducts the user's points balance                                                                                                                    | Required    |
| `billed`    | POS charged the card / payment was captured                     | **Creates the purchase and accrues points.** Reward is marked used.                                                                                                                               | Required    |
| `completed` | Order handed to the customer                                    | Finalizes the order record                                                                                                                                                                        | Recommended |
| `voided`    | Order canceled between `placed` and `billed`                    | Re-delivers locked reward; points products are not re-credited but reward stays available                                                                                                         | Recommended |
| `refunded`  | Order canceled after `billed`                                   | Immediately re-delivers the locked reward, and enqueues a deferred clawback of **accrued loyalty points** for the associated purchase. Points-product balances are not automatically re-credited. | Recommended |

<Warning>
  Points are only created at `billed`. If your POS never transitions to `billed`, no purchase is created and no points accrue — even if `checkout` and `placed` succeeded.
</Warning>

## Three Concepts Every Integration Must Get Right

### 1. Basket ID is partner-generated

The `id` field in the basket payload is an identifier **your system** creates. Thanx does not return a new ID — you reuse the same value across every state transition so the API can correlate the updates to a single basket.

* Generate once when the user starts checkout.
* Reuse for every subsequent request (`placed`, `billed`, `completed`, etc.).
* Only `checkout` requests may omit the ID; every other state requires it.

<Note>
  A common mistake is interpreting the endpoint reference ("Basket ID is not required when submitting a basket with 'checkout' state") to mean the API assigns the ID. It does not. If you generate a new ID for each request, each basket is treated as a separate order and points will not accrue correctly.
</Note>

### 2. `payments[].amount` drives points accrual — not `items[]` or the grand total

Points are calculated from the sum of `payments[].amount` values on the `billed` request. The formula is:

```
payments[].amount = subtotal + tax - discounts
```

This means:

* Include tax; exclude tips.
* Do not send the order's grand total (which includes tips).
* Do not send the items total (which doesn't account for discounts or tax).
* A gift-card **load/reload** is not a purchase — exclude it, so a load-only transaction is correctly \$0 loyalty-eligible. Paying **with** a gift card is a normal purchase and stays eligible.

If `payments` is missing or empty on the `billed` request, no purchase is created. The API will return 201 but nothing lands downstream.

### 3. `items[].price` must include modifier prices

When you send items, the `price` for each item must be the final item price including any modifiers (base price + modifier prices). Sending `price: 0` or the base-only price causes item-level discount rewards to calculate a \$0 discount.

| Scenario                            | Correct `price` |
| ----------------------------------- | --------------- |
| Burger ($10) + Extra cheese ($1.50) | `11.50`         |
| Coffee (\$4) + no modifiers         | `4.00`          |
| Combo ($15) with drink upgrade ($2) | `17.00`         |

## Endpoint URLs: Sandbox vs. Production

The Loyalty API lives on a dedicated subdomain. Using the wrong base URL returns 404s or routes to an unrelated service that will not accept basket requests.

| Environment | Base URL                                |
| ----------- | --------------------------------------- |
| Sandbox     | `https://loyalty.thanxsandbox.com/api/` |
| Production  | `https://loyalty.thanx.com/api/`        |

<Warning>
  Do not use `https://api.thanxsandbox.com/loyalty/*` — it's a different path that is not a supported entry point for basket requests.
</Warning>

## Required Headers

See [Loyalty API Headers](/loyalty/headers) for the full list. Two headers cause the majority of "why are my baskets silently lost" tickets:

* **`Merchant-Key`** — the merchant hashid (e.g., `example-hashid-from-your-rep`), not the numeric merchant ID. Sending a numeric ID (or any value that isn't a valid hashid) returns `401 "The Merchant-Key header is missing or invalid"` — no basket or purchase is created. Your Thanx representative provides the correct key during onboarding.
* **`User-Agent`** — must identify your integration; some auth paths reject generic values.

<Warning>
  Send the **same** `Merchant-Key` on every state transition, and make sure it matches the merchant your end-user token is authenticated against. A `Merchant-Key` that resolves to a different merchant than the token is rejected with `401` — so a shared or multi-brand storefront that sends the wrong brand's key on `billed` sees that call fail rather than silently accruing to the wrong merchant.
</Warning>

## Sandbox Testing: Expected Latency

Sandbox purchase processing is noticeably slower than production. Delays of several minutes up to tens of minutes between a successful `billed` request and the purchase appearing in `GET /account` responses are common based on observed partner testing. Production is near real-time.

<Tip>
  Before reporting "points not accruing" in sandbox, wait and re-check after tens of minutes. Most "missing points" sandbox tickets resolve themselves once the worker catches up.
</Tip>

## Troubleshooting

### Symptom: `billed` returns 201 but no purchase appears

| Likely cause                     | How to verify                                                                  | Fix                                                                          |
| -------------------------------- | ------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- |
| Wrong `Merchant-Key` header      | Check the header value sent against the one your Thanx representative provided | Use the hashid, not the numeric merchant ID                                  |
| Missing `payments[]` on `billed` | Inspect the request body — is `payments` present and non-empty?                | Include `payments` with `amount`, `last4`, `issuer`                          |
| Sandbox latency                  | Confirm time since request                                                     | Re-check after tens of minutes; sandbox processing is slower than production |
| Wrong base URL                   | Check outgoing URL                                                             | Use `loyalty.thanxsandbox.com/api/`, not `api.thanxsandbox.com/loyalty/`     |

### Symptom: Item-level reward discount is \$0

| Likely cause                                           | How to verify                                    | Fix                            |
| ------------------------------------------------------ | ------------------------------------------------ | ------------------------------ |
| `items[].price` does not include modifier prices       | Compare sent `price` against POS receipt         | Send base + modifier total     |
| `items[].id` mismatches the reward template's item IDs | Compare against the reward template's `products` | Use the exact IDs Thanx issued |

### Symptom: Points accrue for the wrong amount

| Likely cause                                 | How to verify                                    | Fix                               |
| -------------------------------------------- | ------------------------------------------------ | --------------------------------- |
| `payments[].amount` includes tips            | Compute `subtotal + tax - discounts` and compare | Send `subtotal + tax - discounts` |
| `payments[].amount` excludes tax             | Same as above                                    | Same                              |
| `payments[].amount` reflects the grand total | Same as above                                    | Same                              |

### Symptom: Basket ID keeps changing between calls

Your system is generating a new UUID per request instead of reusing the one from `checkout`. Generate the ID once when checkout starts, persist it, and send the same value on every state transition.

## Before You Certify

Use the Pre-Certification Self-Check checklist for your integration type ([POS/Kiosk](/overview/guides/pos-kiosk), [Consumer UX](/overview/guides/consumer-ux), [Pay-at-Table](/overview/guides/pay-at-table)) to confirm your basket flow is correct before submitting for certification. The checklist mirrors the scenarios in this guide.

## Related Pages

* [Create & Update Basket (endpoint reference)](/loyalty/create-update-basket)
* [Loyalty API Headers](/loyalty/headers)
* [Loyalty API Overview](/loyalty/overview)
* [Loyalty Models Overview](/overview/loyalty-models)
* [POS/Kiosk Integration Guide](/overview/guides/pos-kiosk)
