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

# Qualifying Rewards for a Basket

> Returns the subset of the user's rewards and points products that would actually produce a discount against a given basket. Read-only dry-run companion to Create & Update Basket — no state is mutated, no reward is redeemed, and no points are debited.

<Tip>
  Use this endpoint to surface "applicable" offers to a customer before checkout (e.g. an offers carousel that only shows rewards the customer can use against the current basket). For the full unfiltered list of what the user owns, use [Get Account](/loyalty/get-account) instead.
</Tip>

<Note>
  Read-only dry-run of [Create & Update Basket](/loyalty/create-update-basket) — same eligibility and discount-calculation logic, no state mutation, no points debit. It accepts the same request body shape, including the flat, top-level `location_uid` string used for location-restricted rewards.
</Note>

## Overview

`POST /baskets/qualifying` is a dry-run companion to [Create & Update Basket](/loyalty/create-update-basket):

* `POST /baskets` runs the full reward redemption pipeline (state machine, validation, side effects).
* `POST /baskets/qualifying` runs the same eligibility and discount calculation logic, but does **not** mutate state, does **not** redeem any reward, and does **not** debit points. It returns the rewards and points products that *would* apply if the basket were submitted as-is.

A reward returned by this endpoint is one that [Create & Update Basket](/loyalty/create-update-basket) would accept for the same basket shape — the two endpoints share the same applicability check.

## HTTP Request

### Parameters

Every field is optional on this endpoint. Unlike [Create & Update Basket](/loyalty/create-update-basket) — where fields such as `location_uid`, `items`, and `subtotal` are required — `/baskets/qualifying` accepts them all optionally; omitting a field simply reduces the precision of the qualifying set (see the per-field notes below). Any field accepted by Create & Update Basket is also accepted here and forwarded into the discount calculation engine unchanged.

<ParamField body="redemption_venue" type="string">
  Either `instore` or `online`. Defaults to `online` when omitted, which only surfaces rewards whose `redemption_venue` is `online` or `all`.
</ParamField>

<ParamField body="order_timestamp" type="string">
  Order timestamp used by time-restricted reward eligibility, in **ISO 8601 (UTC)**. Defaults to the current server time when omitted.
</ParamField>

<ParamField body="location_uid" type="string">
  Thanx Location UID, used to apply reward location restrictions — the same flat, top-level `location_uid` accepted by [Create & Update Basket](/loyalty/create-update-basket).

  A mapping of Thanx location identifiers can be provided by the Thanx developer support team or fetched from the following endpoints depending on your integration type:

  * Consumer API [Get Locations](/consumer/locations/get-locations) endpoint.
  * Partner API [Get Locations](/partner/metadata/get-locations) endpoint.

  <Note>
    Locations belonging to a different merchant than the one the access token resolves to are silently dropped.
  </Note>
</ParamField>

<ParamField body="items" type="array(hash)">
  Basket items. Same shape as [Create & Update Basket](/loyalty/create-update-basket) — modifiers are nested under each item.

  <Expandable title="item">
    <ParamField body="id" type="string">
      Item ID in your system / POS.
    </ParamField>

    <ParamField body="name" type="string">
      Item name.
    </ParamField>

    <ParamField body="price" type="decimal">
      Item amount. Include this so item-redeem rewards (free item, BOGO, modifier-discount) can be calculated — without it, item-type rewards resolve to a `$0` discount and are filtered out of the response.
    </ParamField>

    <ParamField body="categories" type="array(string)">
      Categories that describe this item.
    </ParamField>

    <ParamField body="modifiers" type="array(hash)">
      Array of modifiers for this item.

      <Expandable title="modifier">
        <ParamField body="id" type="string">
          Modifier ID in your system / POS.
        </ParamField>

        <ParamField body="name" type="string">
          Modifier name.
        </ParamField>

        <ParamField body="price" type="decimal">
          Modifier price adjustment.
        </ParamField>

        <ParamField body="item_base_price" type="decimal">
          Base price of the item before modifiers.
        </ParamField>
      </Expandable>
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="subtotal" type="decimal">
  Basket subtotal in USD (pre-discount). Include it so `minimum`-gated rewards can be evaluated — without it, minimum-gated rewards are treated as not-yet-qualifying and filtered out.
</ParamField>

### Response

`200 OK` with a JSON body containing two arrays: `rewards` and `points_products`.

<ResponseField name="rewards" type="array(RewardEntity)">
  Rewards the user owns that (1) pass eligibility (state, ownership, location, time restrictions, handoff, expiration), AND (2) would produce a positive discount against the supplied basket.

  Ordering: effective expiration ascending (soonest-to-expire first); rewards without an expiration are placed last; reward `id` is the tiebreaker.

  <Expandable title="RewardEntity">
    <ResponseField name="id" type="string">
      Reward hashid. Use this as the `reward_id` when calling [Create & Update Basket](/loyalty/create-update-basket).
    </ResponseField>

    <ResponseField name="value" type="decimal">
      Reward value. Interpretation depends on `type`: `amount` → dollars off; `percent` → percentage; `item` → dollar value of the free item; `fixed_price` → target price.
    </ResponseField>

    <ResponseField name="minimum" type="decimal">
      Minimum basket subtotal required for the reward to apply. `null` when there is no minimum.
    </ResponseField>

    <ResponseField name="maximum" type="decimal">
      Maximum discount cap. `null` when there is no cap.
    </ResponseField>

    <ResponseField name="label" type="string">
      Customer-facing reward label (e.g. `"$5 off your next purchase"`).
    </ResponseField>

    <ResponseField name="state" type="string">
      Always `"redeemable"` for rewards in this array.
    </ResponseField>

    <ResponseField name="type" type="string">
      One of `amount`, `percent`, `item`, `fixed_price`.
    </ResponseField>

    <ResponseField name="products" type="array(string)">
      Product IDs the reward is restricted to (item-redeem and BOGO). Empty for unrestricted rewards.
    </ResponseField>

    <ResponseField name="fine_print" type="string">
      Customer-facing fine print (location restrictions, minimums, etc.).
    </ResponseField>

    <ResponseField name="retire_at" type="string">
      Reward expiration (ISO 8601). `null` when the reward does not expire.
    </ResponseField>

    <ResponseField name="restriction_location_ids" type="array(string)">
      Location hashids the reward is restricted to. Empty when the reward is not location-restricted.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="points_products" type="array(PointsProductEntity)">
  Points-exchange products the user can afford AND that would produce a positive discount against the supplied basket.

  <Expandable title="PointsProductEntity">
    <ResponseField name="id" type="string">
      Points product hashid. Use this as the points-product id when calling [Create & Update Basket](/loyalty/create-update-basket).
    </ResponseField>

    <ResponseField name="points" type="integer">
      Points cost to redeem.
    </ResponseField>

    <ResponseField name="value" type="decimal">
      Discount value (same semantics as `RewardEntity.value`).
    </ResponseField>

    <ResponseField name="minimum" type="decimal">
      Minimum basket subtotal. `null` when there is no minimum.
    </ResponseField>

    <ResponseField name="maximum" type="decimal">
      Discount cap. `null` when there is no cap.
    </ResponseField>

    <ResponseField name="label" type="string">
      Customer-facing label.
    </ResponseField>

    <ResponseField name="state" type="string">
      `"redeemable"` or `"unredeemable"`.
    </ResponseField>

    <ResponseField name="type" type="string">
      One of `amount`, `percent`, `item`, `fixed_price`.
    </ResponseField>

    <ResponseField name="products" type="array(string)">
      Restricted product IDs (empty when unrestricted).
    </ResponseField>

    <ResponseField name="fine_print" type="string">
      Customer-facing fine print.
    </ResponseField>

    <ResponseField name="restriction_location_ids" type="array(string)">
      Location restrictions (empty when none).
    </ResponseField>
  </Expandable>
</ResponseField>

### Empty basket behavior

A basket with no `items` and no `subtotal` always returns empty arrays:

```json theme={null}
{ "rewards": [], "points_products": [] }
```

This is intentional — nothing in the user's portfolio can produce a positive discount against a nonexistent basket. Treat empty arrays as "no applicable offers for the current basket" rather than "the user has no offers at all"; for the full unfiltered list, use [Get Account](/loyalty/get-account).

## Notes for implementation

<Tip>
  **Call frequency.** Safe to call on every basket change (item added or removed, quantity changed). The endpoint is read-only and does not mutate user or merchant state.
</Tip>

<Warning>
  **Do not cache responses across baskets.** The qualifying set changes with basket contents — caching defeats the purpose of the endpoint.
</Warning>

* **Item prices**: include `price` on each item so item-redeem rewards (free item, BOGO) are evaluated. Without prices, item-type rewards resolve to a `$0` discount and are filtered out.
* **Location and timestamp**: include `location_uid` and `order_timestamp` when known so location-restricted and time-restricted rewards filter correctly. Omitting these is safe — they default to "no restriction match" / "current server time" — but means location-restricted offers may surface for baskets that ultimately won't be eligible at checkout.
* **Handoff restrictions**: rewards limited to a specific handoff mode (e.g. a delivery-only offer) are returned only when the request body's `handoff` matches the reward's restriction; omit `handoff` and those rewards are filtered out. As with [Create & Update Basket](/loyalty/create-update-basket), `handoff` is read from the request body and applied to reward eligibility.
* **Redemption still requires `POST /baskets`.** A reward returned here is one that [Create & Update Basket](/loyalty/create-update-basket) would accept for the same basket. Calling `/baskets/qualifying` does not redeem anything; partners must still call `/baskets` at checkout to actually apply the reward.

## Error responses

| Status               | When                                                                                                                                             |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `400 Bad Request`    | Validation error on `redemption_venue` (not `instore` / `online`) or malformed `order_timestamp`.                                                |
| `401 Unauthorized`   | Missing or invalid `Authorization` bearer token or `Merchant-Key`, or the `Merchant-Key` resolves to a different merchant than the access token. |
| `406 Not Acceptable` | Missing or wrong `Accept` header. Use `application/vnd.thanx-v1+json`.                                                                           |

<RequestExample>
  ```bash theme={null}
  curl https://loyalty.thanxsandbox.com/api/baskets/qualifying \
    -X POST \
    -H "Authorization: Bearer ${token}" \
    -H "Content-Type: application/json" \
    -H "Accept: application/vnd.thanx-v1+json" \
    -H "Merchant-Key: ${merchant_key}" \
    -d '{
      "redemption_venue": "instore",
      "order_timestamp": "2026-05-20T18:02:05Z",
      "location_uid": "LG-567fhwer",
      "subtotal": 30.00,
      "items": [
        { "id": "burger-001", "name": "Cheeseburger", "price": 22.00 },
        { "id": "fries-001",  "name": "Fries",        "price":  8.00 }
      ]
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "rewards": [
      {
        "id": "MkkhgM",
        "value": 5.0,
        "minimum": 25.0,
        "maximum": null,
        "label": "$5 off your next purchase",
        "state": "redeemable",
        "type": "amount",
        "products": [],
        "fine_print": "Minimum purchase size is $25 for redemption.",
        "retire_at": null,
        "restriction_location_ids": []
      },
      {
        "id": "M6QhQN",
        "value": 10.0,
        "minimum": null,
        "maximum": null,
        "label": "10% off your next purchase",
        "state": "redeemable",
        "type": "percent",
        "products": [],
        "fine_print": "",
        "retire_at": null,
        "restriction_location_ids": []
      }
    ],
    "points_products": [
      {
        "id": "AqdC9L",
        "points": 100,
        "value": 5.0,
        "minimum": 10.0,
        "maximum": null,
        "label": "$5 off your next purchase",
        "state": "redeemable",
        "type": "amount",
        "products": [],
        "fine_print": "Minimum purchase size is $10 for redemption.",
        "restriction_location_ids": []
      }
    ]
  }
  ```

  ```json 400 theme={null}
  {
    "code": 400,
    "message": "redemption_venue must be one of: instore, online"
  }
  ```

  ```json 401 theme={null}
  {
    "code": 401,
    "message": "The Merchant-Key header is missing or invalid"
  }
  ```
</ResponseExample>

## Related Pages

* [Create & Update Basket](/loyalty/create-update-basket) — the redemption endpoint; the same applicability check used here.
* [Get Account](/loyalty/get-account) — full unfiltered list of the user's rewards and affordable points products (no basket filtering).
* [Loyalty API Headers](/loyalty/headers) — required `Authorization`, `Merchant-Key`, and `Accept` headers.
