# Lists booking discount usage records with optional filtering and pagination

`GET /api/configuration/booking-discount-usage`

- **Success:** `200 OK`
- **Products:** Operator (`FOH`), Consumer (`CONSUMER`) (any one of these products, not all)
- **Features:** Front of House Generic Access (`FOHGENERIC`), Online Bookings (`ONLINEBOOK`) (any one of these features, not all)

HTML version: [Lists booking discount usage records with optional filtering and pagination](https://support.gladstonesoftware.cloud/reference/configuration/lists-booking-discount-usage-records-with-optional-filtering-and-pagination)

## Overview

Returns a history of booking discounts that have actually been taken: one record per discounted
booking line, with the promotion code used, the price before and after the discount, and the member
and booking it applied to. Use it to audit promotion use or to show a member why a booking cost what
it cost. It reports past use only — to find out which discounts are *available*, call
[List booking discounts for an activity and date](lists-all-booking-discounts-for-a-given-activity-and-date.md).

**At least one filter is required.** Calling with no parameters at all fails with `400`; there is no
way to page through every record. Filters combine with AND, and every id you filter on must exist —
filtering on a member, booking, activity or promotion code that does not exist is a `400`, not an
empty result.

**This endpoint is gated behind the `BOOKINGDISCOUNTS` feature.** Without it, every call returns
`403`.

Results are paged with a continuation token rather than an offset. Follow `links.next` until it is
`null`; do not construct the token yourself. Because a token is emitted whenever a page comes back
full, the last page of an exact multiple of `limit` will hand you a link that returns an empty
`data` array — that is the normal way the sequence ends, not an error.

## Endpoint

`GET {base_url}/api/configuration/booking-discount-usage`

`{base_url}` is the environment URL issued to your integration. See [Authenticating with Gladstone APIs](../../guides/authenticating-with-gladstone-apis.md).

## Query parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `MemberID` | integer (int32) | No | Restricts results to discounts taken on bookings this member is on. The member must exist or the call fails with `400` and `Member with ID '{MemberID}' does not exist.` — an existing member with no discounted bookings returns `200` with an empty `data` array instead. Note the capitalised parameter name. |
| `BookingID` | integer (int32) | No | Restricts results to one booking. The booking must exist or the call fails with `400` and `Booking with ID '{BookingID}' does not exist.` Combines with the other filters using AND, so a booking that does not also match them returns nothing. |
| `ActivityID` | string | No | Restricts results to bookings of one activity, matched exactly against the booking's activity id — no partial or case-insensitive matching. The activity must exist or the call fails with `400` and `Activity with ID '{ActivityID}' does not exist.` |
| `PromotionCode` | string | No | Restricts results to one promotion code, matched exactly against the code string rather than an id. The code must exist or the call fails with `400` and `Promotion code '{PromotionCode}' does not exist.` |
| `DateFrom` | string (date-time) | No | Keeps only records whose **booking start** date is on or after this date. Only the date part is used, so a time component has no effect and the boundary day is always included. May be used without `DateTo`, in which case there is no upper bound. |
| `DateTo` | string (date-time) | No | Keeps only records whose **booking end** date is on or before this date — note it tests the end of the booking, while `DateFrom` tests the start, so a booking spanning midnight can fall outside a single-day range. Only the date part is used. If both are supplied and `DateTo` is earlier than `DateFrom`, the call fails with `400` and `DateTo cannot be before DateFrom. Please enter a valid date.` |
| `continuation-token` | string | No | Take this from `links.next` of the previous response rather than building it. Its form is `discount-usage-id:{id}` and the next page starts strictly after that record id. Omit it for the first page. It is parsed before anything else is validated, so a malformed value fails with `400` — `Invalid continuation token format` when it is not two colon-separated parts, `Invalid continuation token key` when the part before the colon is not `discount-usage-id`, and `Invalid continuation token value` when the part after it is not an integer. Repeat the same filters alongside it; changing them mid-sequence silently changes what you are paging through. |
| `limit` | integer (int32) | No | Maximum records in `data`. Defaults to `50` when omitted. It is not validated: **`limit=0` fails with `500`**, because an empty page is mistaken for a full one. There is no documented upper bound, so a very large value is not capped. |

## Sample request

```bash title="cURL"
curl -X GET '{base_url}/api/configuration/booking-discount-usage' \
  -H 'Authorization: Bearer {accessToken}' \
  -H 'X-Use-SSO: true'
```

## Response fields

| Field | Type | Description |
|-------|------|-------------|
| `data` | array of object | The page of usage records, ordered by `id` ascending. Empty rather than `null` when nothing matches. Its length is at most `limit`; a length equal to `limit` is what causes `links.next` to be issued. |
| `data[].id` | integer (int32) | Identifies the discount-usage record, and doubles as the pagination cursor — the last one on a page becomes the continuation token. **It is not unique within the response**: a single usage record spanning more than one sales invoice or booking member produces one entry per combination. Because the next page starts strictly after the last entry's `id`, a group of entries sharing an `id` that straddles a page boundary loses its remainder; use a `limit` comfortably larger than any such group. |
| `data[].promotionCode` | string | The promotion code string that was applied, not its id. It is the same value the `PromotionCode` filter matches on. |
| `data[].priceLevelCombo` | string | The price-level combination that qualified for the discount, returned **as a single raw string** — at most 200 characters, in a bracketed, comma-separated form. Beware: the identically named field on [List booking discounts](lists-all-booking-discounts-for-a-given-activity-and-date.md) is an array of strings parsed from this same value, so the two endpoints report the same data in different shapes. Parse it yourself if you need the individual price levels. |
| `data[].originalPrice` | number (double) | The combined price the booking would have cost without the discount, held to 2 decimal places. Compare with `discountedPrice` to derive the saving — the saving itself is not returned. |
| `data[].discountedPrice` | number (double) | The combined price actually charged after the discount, held to 2 decimal places. This is the whole combination's price, not a per-person figure. |
| `data[].discountSalesInvoiceId` | integer (int32) | The sales invoice line recording the discount itself. |
| `data[].memberId` | integer (int32) | The member on the booking this entry relates to. Where a discounted booking covers several members, each produces its own entry against the same usage `id`, so summing prices across entries double-counts. |
| `data[].bookingId` | integer (int32) | The booking the discount was applied to — the same identifier the `BookingID` filter takes. |
| `data[].bookingRef` | integer (int32) | The booking's reference number as held against the member's place on it. Nullable, so expect `null` on older records. |
| `data[].bookingStartDateTime` | string (date-time) | When the booking starts. This is the value `DateFrom` filters on. Nullable — a booking with no start recorded is still returned, and is excluded by any `DateFrom` filter. |
| `data[].bookingEndDateTime` | string (date-time) | When the booking ends. This is the value `DateTo` filters on. Nullable, with the same consequence as for the start. |
| `links` | object | Pagination links. Always present, even on the last page. |
| `links.next` | string | The absolute URL of the next page: this endpoint's public URL with your filters re-applied, plus `continuation-token` and `limit`. Follow it as-is. It is `null` when the page came back shorter than `limit`, which is the only reliable end-of-sequence signal — a page that is exactly full always yields a link, even when no records remain. |

## Sample response

```json title="200 OK"
{
  "data": [
    {
      "id": 12345,
      "promotionCode": "string",
      "priceLevelCombo": "string",
      "originalPrice": 10.5,
      "discountedPrice": 10.5,
      "discountSalesInvoiceId": 12345,
      "memberId": 12345,
      "bookingId": 12345,
      "bookingRef": 1,
      "bookingStartDateTime": "2026-08-14T19:00:00Z",
      "bookingEndDateTime": "2026-08-14T19:00:00Z"
    }
  ],
  "links": {
    "next": "string"
  }
}
```

## Errors

| Status | Meaning | When |
|--------|---------|------|
| `400` | Bad request | One of: no filter at all — `At least one of the following query parameters must be provided: MemberID, BookingID, ActivityID, PromotionCode, DateFrom, DateTo.`; `DateTo` before `DateFrom`; a malformed `continuation-token`; or a `MemberID`, `BookingID`, `ActivityID` or `PromotionCode` that does not exist, each with its own message naming the value. The body is the plain message string, not JSON. |
| `403` | Forbidden | Your token does not carry the `BOOKINGDISCOUNTS` feature. The body is the plain string `The requested operation requires Module BOOKINGDISCOUNTS to be enabled`. Contact Gladstone Support to have the feature enabled for your integration. |
| `500` | Internal server error | Any unexpected server-side failure, with an empty body. Two causes are reachable from the request itself: `limit=0`, and a request that does not come through the gateway, which leaves the endpoint unable to build `links.next`. |
