# Gets a summary of bookings for a member, including discount information

`GET /api/members/{memberId}/bookings-summary`

- **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: [Gets a summary of bookings for a member, including discount information](https://support.gladstonesoftware.cloud/reference/members/gets-a-summary-of-bookings-for-a-member-including-discount-information)

## Overview

Assembles, in one call, everything a member's "my bookings" screen needs: their recent past bookings, their upcoming bookings, and the promotion figures behind each. The endpoint looks up member links, bookings and discount records on your behalf, and folds the results into a single object with two arrays, `bookingHistory` and `futureBookings`.

**The history window is fixed at the last 30 days.** It covers 30 days before today up to the moment of the call. There is no query parameter here to widen it, so a booking from two months ago will not appear no matter what you send. If you need older bookings, or paging, use [List all past bookings with pagination (v2.0)](list-all-past-bookings-with-pagination-v20.md) instead. `futureBookings` has no equivalent limit: it reaches as far ahead as the member has booked.

Linked members are included, but only in one direction. The endpoint reads the requested member's links and, **if that member is the primary of their link group**, also fetches bookings for each linked member. A secondary member calling for themselves gets only their own bookings — they cannot see the primary's. Bookings belonging to a linked member are marked by `keyBookeeMemberId` and `name`, which is how you tell whose booking you are looking at.

Bookings are then grouped by [slot reference](../../guides/glossary.md), so that one underlying booking held by two people in the same link group appears as one group with two entries rather than as a duplicate. History groups come back most recent first; future groups come back soonest first. Both orderings are taken from the first booking in each group.

Only bookings held against a standard resource — a court, a lane, a room — are returned. Bookings that occupy capacity rather than a named resource are omitted entirely, silently, from both arrays.

Discount enrichment is best-effort and fails open. If the booking discount feature is not enabled for the tenant, the call still succeeds and every `discountDetails` comes back null; a null therefore tells you nothing about whether a promotion was applied. Discount figures are also withheld for any group where the requested member is not the member who made the bookings. Read the `discountDetails` field notes before you show these amounts to anyone — they come from the promotion's own records rather than from the booking's invoice.

You must ask for at least one of the two arrays: sending both `includeBookingHistory=false` and `includeFutureBookings=false` is rejected with `400`. There is no pagination and no filtering, and the work grows with the size of the link group — a primary member with several linked members produces two lookups per member plus a paged sweep of discount records, so treat this as a page-load call rather than something to poll.

## Endpoint

`GET {base_url}/api/members/{memberId}/bookings-summary`

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

## Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `memberId` | integer (int32) | Yes | The member whose bookings you want. If this member is the primary of a link group, the bookings of every member linked to them are returned as well; if they are a secondary, only their own are. Must be `1` or greater, or the call is rejected with `400` and the message `{memberId} is an invalid member ID`. |

## Query parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `includeBookingHistory` | boolean | No | Whether to populate `bookingHistory`. Defaults to `true`. Set it to `false` to skip the past-bookings lookups entirely — `bookingHistory` then comes back as an empty array rather than being absent. Cannot be `false` at the same time as `includeFutureBookings`. |
| `includeFutureBookings` | boolean | No | Whether to populate `futureBookings`. Defaults to `true`. Set it to `false` to skip the upcoming-bookings lookups — `futureBookings` then comes back as an empty array rather than being absent. Cannot be `false` at the same time as `includeBookingHistory`. |

## Sample request

```bash title="cURL"
curl -X GET '{base_url}/api/members/{memberId}/bookings-summary' \
  -H 'Authorization: Bearer {accessToken}' \
  -H 'X-Use-SSO: true'
```

## Response fields

| Field | Type | Description |
|-------|------|-------------|
| `bookingHistory` | array of object | Past bookings, grouped by [slot reference](../../guides/glossary.md), most recent group first. **Covers the last 30 days only** — the window is fixed and cannot be changed from this endpoint. An empty array when `includeBookingHistory` is `false`, and equally when the member simply has nothing in the last 30 days; the two cases are indistinguishable. |
| `bookingHistory[].bookings` | array of object | The past bookings sharing this group's slot reference. More than one entry means the requested member and one of their linked members each hold a place on the same booking — that is what the grouping exists for. Every entry has the same `slotReference`, and the group's position in the array is taken from the first entry's `dateTime`. |
| `bookingHistory[].bookings[].bookingId` | integer (int32) | The identifier of the underlying booking — one activity occurrence at one time. Every member holding a place on that booking reports the same value, which is why a single group can contain several entries. This is the key to use when matching against the other booking endpoints. |
| `bookingHistory[].bookings[].bookingRef` | integer (int32) | A reference identifying this member's place on the booking, distinct from `bookingId`. It is what the sales invoice and, for a party activity, the individual party lines are filed under, so it is the value that ties this booking to its money. Do not use it interchangeably with `bookingId`. |
| `bookingHistory[].bookings[].slotReference` | string | The [slot reference](../../guides/glossary.md) for the booking, and the key this group was formed on. Treat it as an opaque string: it is a JSON document encoded as a string and its internal structure is not part of the contract. Where a full reference could not be resolved, a minimal one built from `bookingId` alone is returned instead — which still groups correctly, but carries less inside it. |
| `bookingHistory[].bookings[].name` | string | The full name of the member this booking belongs to — the member named by `keyBookeeMemberId`, which for a linked member is not the member in the request path. Formed as first names followed by last name. Null when that member's record could not be read. |
| `bookingHistory[].bookings[].keyBookeeMemberId` | integer (int32) | The member whose booking this is: either the `memberId` you requested, or one of their linked members. Use it to attribute bookings within a group. It also gates the group's discount figures — `discountDetails` is only populated when the group's first booking belongs to the member you requested. |
| `bookingHistory[].bookings[].activityId` | string | The identifier of the activity booked — badminton, a swim session, a fitness class. Stable across bookings of the same activity, so it is the value to key on when you need activity configuration this response does not carry. |
| `bookingHistory[].bookings[].activityName` | string | The activity's short description as configured at the site, untranslated. Translated forms are in `translations`. |
| `bookingHistory[].bookings[].activityDescription` | string | The activity's long description as configured at the site, untranslated. A separate, fuller field than `activityName`, not a translation of it. |
| `bookingHistory[].bookings[].dateTime` | string (date-time) | The booking's start date and time. This is what history groups are ordered on, most recent first, and what the fixed 30-day window is applied to. |
| `bookingHistory[].bookings[].siteId` | string | The identifier of the site — the centre — the activity runs at. |
| `bookingHistory[].bookings[].location` | object | The resource the booking occupies — the court, lane or room. Where a booking covers several resources, only the first one found is reported. |
| `bookingHistory[].bookings[].location.id` | string | The product identifier of the resource, the same `productId` used by the product configuration endpoints. |
| `bookingHistory[].bookings[].location.description` | string | The resource's description as configured, untranslated. Translated forms are in `location.translations`. |
| `bookingHistory[].bookings[].location.translations` | array of object | Translated forms of `location.description`. An empty array where the resource has no translations configured. |
| `bookingHistory[].bookings[].location.translations[].type` | string | The name of the property this text translates. The descriptions returned by this endpoint are translated under `description`. |
| `bookingHistory[].bookings[].location.translations[].culture` | string | The culture this text is for, as recorded against the translation — for example `en-GB` or `cy-GB`. |
| `bookingHistory[].bookings[].location.translations[].text` | string | The translated text. Match on `culture` and `type` to pick the one you want; there is no ordering guarantee and no fallback entry, so keep the untranslated sibling field as your default. |
| `bookingHistory[].bookings[].attendedInCentre` | boolean | Whether the member was recorded as having attended in person. `true` only when attendance was marked **and** the activity is not configured as virtual-only, so it stays `false` for any booking nobody registered attendance against. |
| `bookingHistory[].bookings[].attendedVirtually` | boolean | Whether the member was recorded as having attended online. `true` only when attendance was marked **and** the activity is not configured as in-centre-only. Note the consequence: for an activity offered both ways, marking attendance sets **both** this and `attendedInCentre` to `true`. The pair reflects how the activity could be delivered, not which way the member actually turned up, so do not read one of them as proof of the other being false. |
| `bookingHistory[].bookings[].keyBookeeIncludedInd` | boolean | Whether the member who made the booking is taking one of the booked places themselves, rather than booking purely on other people's behalf. Set from whether they were marked as attending at the time the booking was created. `keyBookeeBookingInfo` is populated only when this is `true`. |
| `bookingHistory[].bookings[].paymentInfo` | object | The sales invoice raised for this member's place on the booking. Always present for a history booking, because history leaves out bookings that have no invoice. |
| `bookingHistory[].bookings[].paymentInfo.salesInvoiceId` | integer (int32) | The sales invoice this member's place on the booking was billed on. Found through `bookingRef`, not `bookingId`. |
| `bookingHistory[].bookings[].paymentInfo.salesInvoiceTotal` | number (double) | The invoice total — the amount charged for this member's place. For a non-party booking this same figure is repeated in `bookeePriceLevels[].price` and, where the booking member is attending, in `keyBookeeBookingInfo.price`. No currency is returned with it, and the response does not say whether it includes tax. |
| `bookingHistory[].bookings[].paymentInfo.salesInvoiceStatus` | string | The invoice status as a name — one of `Unpaid`, `PartPaid`, `Paid`, `Void`, `PartCredited`, `PartCreditedPaid`, `Credited`, `CreditNote` or `Pending`. Compare on the exact string; the numeric codes behind these names are not returned. |
| `bookingHistory[].bookings[].paymentInfo.salesReceiptId` | integer (int32) | The receipt the invoice was settled against, where one exists. Null on an unpaid or unallocated invoice. Where more than one receipt was allocated to the invoice, only the first is reported. |
| `bookingHistory[].bookings[].translations` | array of object | Translated forms of the activity's description. An empty array where the activity has no translations configured. |
| `bookingHistory[].bookings[].translations[].type` | string | The name of the property this text translates. The descriptions returned by this endpoint are translated under `description`. |
| `bookingHistory[].bookings[].translations[].culture` | string | The culture this text is for, as recorded against the translation — for example `en-GB` or `cy-GB`. |
| `bookingHistory[].bookings[].translations[].text` | string | The translated text. Match on `culture` and `type` to pick the one you want; there is no ordering guarantee and no fallback entry, so keep the untranslated sibling field as your default. |
| `bookingHistory[].bookings[].bookeePriceLevels` | array of object | How the charge breaks down by [price level](../../guides/glossary.md), and the shape depends on the activity. For a party activity there is one entry per price level, giving the number of people at that level and their combined total. For every other activity there is **exactly one** entry, whose `count` is hard-coded to `1` and whose `price` is the whole sales invoice total rather than a per-person price. Never multiply `count` by `price`. |
| `bookingHistory[].bookings[].bookeePriceLevels[].id` | string | The price level identifier — adult, junior, concession and so on. For a non-party booking this is the first price level found on the booking's invoice, and it is the only one reported even where the invoice carries several. |
| `bookingHistory[].bookings[].bookeePriceLevels[].description` | string | The price level's display description. Where the site and activity have a description override configured for this price level, the override replaces the global description here. |
| `bookingHistory[].bookings[].bookeePriceLevels[].count` | integer (int32) | The number of people at this price level. Meaningful for party activities only; for every other activity it is always `1`, whatever number of places the booking actually covers. |
| `bookingHistory[].bookings[].bookeePriceLevels[].price` | number (double) | The total charged at this price level, not a unit price. For a party activity it is the sum across everyone at that level. For any other activity it is the whole sales invoice total, repeated from `paymentInfo.salesInvoiceTotal`. |
| `bookingHistory[].bookings[].bookeePriceLevels[].translations` | array of object | Translated forms of the price level's `description`. Where a site-and-activity description override applies, its translations are returned here in place of the global ones. |
| `bookingHistory[].bookings[].bookeePriceLevels[].translations[].type` | string | The name of the property this text translates. The descriptions returned by this endpoint are translated under `description`. |
| `bookingHistory[].bookings[].bookeePriceLevels[].translations[].culture` | string | The culture this text is for, as recorded against the translation — for example `en-GB` or `cy-GB`. |
| `bookingHistory[].bookings[].bookeePriceLevels[].translations[].text` | string | The translated text. Match on `culture` and `type` to pick the one you want; there is no ordering guarantee and no fallback entry, so keep the untranslated sibling field as your default. |
| `bookingHistory[].bookings[].keyBookeeBookingInfo` | object | The price level and amount attributable to the member who made the booking. Populated only when `keyBookeeIncludedInd` is `true`, and null otherwise. For a party activity it is read from the party line flagged as theirs; for any other activity it is a copy of the single `bookeePriceLevels` entry, so it repeats the whole invoice total rather than their share of it. |
| `bookingHistory[].bookings[].keyBookeeBookingInfo.priceLevelId` | string | The price level applied to the booking member's own place. |
| `bookingHistory[].bookings[].keyBookeeBookingInfo.price` | number (double) | The amount recorded against that place. For a party booking whose recorded total is zero — which is how bookings created in the legacy Plus2 system arrive, because that system did not record it — the endpoint recalculates the figure by summing the invoice lines at the same price level, and reports `0` when there are none. For a non-party booking this is the whole invoice total, not the member's share. |
| `bookingHistory[].discountDetails` | object | Promotion figures for this group, aggregated from the discount usage records. Null more often than you would expect, and a null tells you nothing on its own: you get null when the group has no discount records, when the booking discount feature is not enabled for the tenant (in which case every `discountDetails` in the response is null and the call still returns `200`), and when the member you requested is not the member who made the bookings in this group. Where a group's discount records span several members, only the requested member's records are counted — or, if none of them are theirs, those of whichever member appears first. |
| `bookingHistory[].discountDetails.originalPrice` | number (double) | The total before the promotion was applied, summed across this group's discount records. It comes from the promotion's own price-level combination pricing, which is recorded separately from the booking's invoice, so do not assume it reconciles with `paymentInfo.salesInvoiceTotal`. No currency is returned alongside it, and the response does not say whether the figure includes tax. |
| `bookingHistory[].discountDetails.discountedPrice` | number (double) | The equivalent total after the promotion was applied, summed across the same records. The same caveats as `originalPrice` apply: separate from the invoice, no currency, tax treatment unstated. |
| `bookingHistory[].discountDetails.discountAmount` | number (double) | The saving: `originalPrice` minus `discountedPrice`, computed per record and then summed. It always reconciles with the other two fields, even though none of the three is guaranteed to match the invoice. |
| `bookingHistory[].discountDetails.discountSalesInvoiceIds` | array of integer | The distinct sales invoice ids recorded against this group's discount usage entries — the invoices the discount itself was filed against. The response does not say how these relate to `paymentInfo.salesInvoiceId`, so do not assume they are the same invoices. |
| `bookingHistory[].discountDetails.promotionCodes` | array of string | The distinct promotion codes applied across the group, with blank entries dropped. This can be an empty array while the amount fields are populated, where the underlying records carry no code. |
| `futureBookings` | array of object | Bookings starting after the moment of the call, grouped by [slot reference](../../guides/glossary.md), soonest group first. No upper bound — it reaches as far ahead as the member has booked. An empty array when `includeFutureBookings` is `false`, and equally when the member has nothing booked. |
| `futureBookings[].bookings` | array of object | The upcoming bookings sharing this group's slot reference. More than one entry means the requested member and one of their linked members each hold a place on the same booking. Every entry has the same `slotReference`, and the group's position in the array is taken from the first entry's `dateTime`. |
| `futureBookings[].bookings[].bookingUuid` | string (uuid) | The identifier of the booking lease this booking was created from, taken from the most recently created lease that has not yet expired. Null once every lease behind the booking has expired, which is the normal state for a booking made some time ago — so treat its absence as expected, not as an error. |
| `futureBookings[].bookings[].bookingId` | integer (int32) | The identifier of the underlying booking — one activity occurrence at one time. Every member holding a place on that booking reports the same value, which is why a single group can contain several entries. This is the key to use when matching against the other booking endpoints. |
| `futureBookings[].bookings[].bookingRef` | integer (int32) | A reference identifying this member's place on the booking, distinct from `bookingId`. It is what the sales invoice and, for a party activity, the individual party lines are filed under, so it is the value that ties this booking to its money. Do not use it interchangeably with `bookingId`. May be null on a future booking that has not been assigned one. |
| `futureBookings[].bookings[].slotReference` | string | The [slot reference](../../guides/glossary.md) for the booking, and the key this group was formed on. Treat it as an opaque string: it is a JSON document encoded as a string and its internal structure is not part of the contract. Where a full reference could not be resolved, a minimal one built from `bookingId` alone is returned instead — which still groups correctly, but carries less inside it. |
| `futureBookings[].bookings[].name` | string | The full name of the member this booking belongs to — the member named by `keyBookeeMemberId`, which for a linked member is not the member in the request path. Formed as first names followed by last name. Null when that member's record could not be read. |
| `futureBookings[].bookings[].keyBookeeMemberId` | integer (int32) | The member whose booking this is: either the `memberId` you requested, or one of their linked members. Use it to attribute bookings within a group. It also gates the group's discount figures — `discountDetails` is only populated when the group's first booking belongs to the member you requested. |
| `futureBookings[].bookings[].activityId` | string | The identifier of the activity booked — badminton, a swim session, a fitness class. Stable across bookings of the same activity, so it is the value to key on when you need activity configuration this response does not carry. |
| `futureBookings[].bookings[].activityName` | string | The activity's short description as configured at the site, untranslated. Translated forms are in `translations`. |
| `futureBookings[].bookings[].activityDescription` | string | The activity's long description as configured at the site, untranslated. A separate, fuller field than `activityName`, not a translation of it. |
| `futureBookings[].bookings[].imageUrl` | string | A picture for the activity. Where the activity's configured image reference is already an absolute URL beginning `http`, it is returned unchanged; otherwise it is expanded into a public CDN URL for your tenant. Null or empty when the activity has no image configured. |
| `futureBookings[].bookings[].dateTime` | string (date-time) | The booking's start date and time. This is what future groups are ordered on, soonest first. |
| `futureBookings[].bookings[].endDateTime` | string (date-time) | The booking's end date and time. |
| `futureBookings[].bookings[].siteId` | string | The identifier of the site — the centre — the activity runs at. |
| `futureBookings[].bookings[].siteName` | string | The site's display description, untranslated. Returned for future bookings only — history bookings carry `siteId` alone, so if you show a site name on both you must source it elsewhere for history. |
| `futureBookings[].bookings[].siteTranslations` | array of object | Translated forms of `siteName`. An empty array where the site has no translations configured. |
| `futureBookings[].bookings[].siteTranslations[].type` | string | The name of the property this text translates. The descriptions returned by this endpoint are translated under `description`. |
| `futureBookings[].bookings[].siteTranslations[].culture` | string | The culture this text is for, as recorded against the translation — for example `en-GB` or `cy-GB`. |
| `futureBookings[].bookings[].siteTranslations[].text` | string | The translated text. Match on `culture` and `type` to pick the one you want; there is no ordering guarantee and no fallback entry, so keep the untranslated sibling field as your default. |
| `futureBookings[].bookings[].isRefundable` | boolean | Whether the booking meets the conditions for an automated refund. `true` only when the sales invoice is `Paid`, was taken through Gladstone's own payment provider, and carries a payment provider reference — or when the invoice total is zero. A booking paid by any other means reports `false` even though it was genuinely paid for, so this is a statement about how the money can be returned, not about entitlement to a refund. Null when no payment information could be resolved. |
| `futureBookings[].bookings[].keyBookeeIncludedInd` | boolean | Whether the member who made the booking is taking one of the booked places themselves, rather than booking purely on other people's behalf. Set from whether they were marked as attending at the time the booking was created. `keyBookeeBookingInfo` is populated only when this is `true`. |
| `futureBookings[].bookings[].location` | object | The resource the booking occupies — the court, lane or room. Where a booking covers several resources, only the first one found is reported. |
| `futureBookings[].bookings[].location.id` | string | The product identifier of the resource, the same `productId` used by the product configuration endpoints. |
| `futureBookings[].bookings[].location.description` | string | The resource's description as configured, untranslated. Translated forms are in `location.translations`. |
| `futureBookings[].bookings[].location.translations` | array of object | Translated forms of `location.description`. An empty array where the resource has no translations configured. |
| `futureBookings[].bookings[].location.translations[].type` | string | The name of the property this text translates. The descriptions returned by this endpoint are translated under `description`. |
| `futureBookings[].bookings[].location.translations[].culture` | string | The culture this text is for, as recorded against the translation — for example `en-GB` or `cy-GB`. |
| `futureBookings[].bookings[].location.translations[].text` | string | The translated text. Match on `culture` and `type` to pick the one you want; there is no ordering guarantee and no fallback entry, so keep the untranslated sibling field as your default. |
| `futureBookings[].bookings[].inCentre` | object | Confirmation state for an in-centre booking. Only one of `inCentre` and `virtual` carries real values: whichever matches how this booking was made. The other is returned with both of its flags `false`, which is not the same as being unconfirmed — check the right one of the pair before reading either flag. |
| `futureBookings[].bookings[].inCentre.bookingConfirmed` | boolean | Whether the member holds a confirmed place rather than a waiting-list place. Always the exact opposite of `onAlertList` in the same object. Reported as `false` in whichever of `inCentre` / `virtual` does not apply to this booking. |
| `futureBookings[].bookings[].inCentre.onAlertList` | boolean | Whether the member is on the waiting list for this booking rather than holding a place. Always the exact opposite of `bookingConfirmed` in the same object. Reported as `false` in whichever of `inCentre` / `virtual` does not apply to this booking. |
| `futureBookings[].bookings[].virtual` | object | Confirmation state for a virtual booking. Only one of `inCentre` and `virtual` carries real values: whichever matches how this booking was made. The other is returned with both of its flags `false`, which is not the same as being unconfirmed — check the right one of the pair before reading either flag. |
| `futureBookings[].bookings[].virtual.bookingConfirmed` | boolean | Whether the member holds a confirmed place rather than a waiting-list place. Always the exact opposite of `onAlertList` in the same object. Reported as `false` in whichever of `inCentre` / `virtual` does not apply to this booking. |
| `futureBookings[].bookings[].virtual.onAlertList` | boolean | Whether the member is on the waiting list for this booking rather than holding a place. Always the exact opposite of `bookingConfirmed` in the same object. Reported as `false` in whichever of `inCentre` / `virtual` does not apply to this booking. |
| `futureBookings[].bookings[].paymentInfo` | object | The sales invoice raised for this member's place, looked up separately from the booking itself. Where no matching invoice is found, only `salesInvoiceId` is populated and the remaining fields stay at their defaults — a `salesInvoiceTotal` of `0` and a null `salesInvoiceStatus` mean "not found", not "free". |
| `futureBookings[].bookings[].paymentInfo.salesInvoiceId` | integer (int32) | The sales invoice this member's place on the booking was billed on. Found through `bookingRef`, not `bookingId`. |
| `futureBookings[].bookings[].paymentInfo.salesInvoiceTotal` | number (double) | The invoice total — the amount charged for this member's place. For a non-party booking this same figure is repeated in `bookeePriceLevels[].price` and, where the booking member is attending, in `keyBookeeBookingInfo.price`. No currency is returned with it, and the response does not say whether it includes tax. |
| `futureBookings[].bookings[].paymentInfo.salesInvoiceStatus` | string | The invoice status as a name — one of `Unpaid`, `PartPaid`, `Paid`, `Void`, `PartCredited`, `PartCreditedPaid`, `Credited`, `CreditNote` or `Pending`. Compare on the exact string; the numeric codes behind these names are not returned. |
| `futureBookings[].bookings[].paymentInfo.salesReceiptId` | integer (int32) | The receipt the invoice was settled against, where one exists. Null on an unpaid or unallocated invoice. Where more than one receipt was allocated to the invoice, only the first is reported. |
| `futureBookings[].bookings[].translations` | array of object | Translated forms of the activity's description. An empty array where the activity has no translations configured. |
| `futureBookings[].bookings[].translations[].type` | string | The name of the property this text translates. The descriptions returned by this endpoint are translated under `description`. |
| `futureBookings[].bookings[].translations[].culture` | string | The culture this text is for, as recorded against the translation — for example `en-GB` or `cy-GB`. |
| `futureBookings[].bookings[].translations[].text` | string | The translated text. Match on `culture` and `type` to pick the one you want; there is no ordering guarantee and no fallback entry, so keep the untranslated sibling field as your default. |
| `futureBookings[].bookings[].bookeePriceLevels` | array of object | How the charge breaks down by [price level](../../guides/glossary.md), and the shape depends on the activity. For a party activity there is one entry per price level, giving the number of people at that level and their combined total. For every other activity there is **exactly one** entry, whose `count` is hard-coded to `1` and whose `price` is the whole sales invoice total rather than a per-person price. Never multiply `count` by `price`. |
| `futureBookings[].bookings[].bookeePriceLevels[].id` | string | The price level identifier — adult, junior, concession and so on. For a non-party booking this is the first price level found on the booking's invoice, and it is the only one reported even where the invoice carries several. |
| `futureBookings[].bookings[].bookeePriceLevels[].description` | string | The price level's display description. Where the site and activity have a description override configured for this price level, the override replaces the global description here. |
| `futureBookings[].bookings[].bookeePriceLevels[].count` | integer (int32) | The number of people at this price level. Meaningful for party activities only; for every other activity it is always `1`, whatever number of places the booking actually covers. |
| `futureBookings[].bookings[].bookeePriceLevels[].price` | number (double) | The total charged at this price level, not a unit price. For a party activity it is the sum across everyone at that level. For any other activity it is the whole sales invoice total, repeated from `paymentInfo.salesInvoiceTotal`. |
| `futureBookings[].bookings[].bookeePriceLevels[].translations` | array of object | Translated forms of the price level's `description`. Where a site-and-activity description override applies, its translations are returned here in place of the global ones. |
| `futureBookings[].bookings[].bookeePriceLevels[].translations[].type` | string | The name of the property this text translates. The descriptions returned by this endpoint are translated under `description`. |
| `futureBookings[].bookings[].bookeePriceLevels[].translations[].culture` | string | The culture this text is for, as recorded against the translation — for example `en-GB` or `cy-GB`. |
| `futureBookings[].bookings[].bookeePriceLevels[].translations[].text` | string | The translated text. Match on `culture` and `type` to pick the one you want; there is no ordering guarantee and no fallback entry, so keep the untranslated sibling field as your default. |
| `futureBookings[].bookings[].keyBookeeBookingInfo` | object | The price level and amount attributable to the member who made the booking. Populated only when `keyBookeeIncludedInd` is `true`, and null otherwise. For a party activity it is read from the party line flagged as theirs; for any other activity it is a copy of the single `bookeePriceLevels` entry, so it repeats the whole invoice total rather than their share of it. |
| `futureBookings[].bookings[].keyBookeeBookingInfo.priceLevelId` | string | The price level applied to the booking member's own place. |
| `futureBookings[].bookings[].keyBookeeBookingInfo.price` | number (double) | The amount recorded against that place. For a party booking whose recorded total is zero — which is how bookings created in the legacy Plus2 system arrive, because that system did not record it — the endpoint recalculates the figure by summing the invoice lines at the same price level, and reports `0` when there are none. For a non-party booking this is the whole invoice total, not the member's share. |
| `futureBookings[].discountDetails` | object | Promotion figures for this group, aggregated from the discount usage records. Null more often than you would expect, and a null tells you nothing on its own: you get null when the group has no discount records, when the booking discount feature is not enabled for the tenant (in which case every `discountDetails` in the response is null and the call still returns `200`), and when the member you requested is not the member who made the bookings in this group. Where a group's discount records span several members, only the requested member's records are counted — or, if none of them are theirs, those of whichever member appears first. |
| `futureBookings[].discountDetails.originalPrice` | number (double) | The total before the promotion was applied, summed across this group's discount records. It comes from the promotion's own price-level combination pricing, which is recorded separately from the booking's invoice, so do not assume it reconciles with `paymentInfo.salesInvoiceTotal`. No currency is returned alongside it, and the response does not say whether the figure includes tax. |
| `futureBookings[].discountDetails.discountedPrice` | number (double) | The equivalent total after the promotion was applied, summed across the same records. The same caveats as `originalPrice` apply: separate from the invoice, no currency, tax treatment unstated. |
| `futureBookings[].discountDetails.discountAmount` | number (double) | The saving: `originalPrice` minus `discountedPrice`, computed per record and then summed. It always reconciles with the other two fields, even though none of the three is guaranteed to match the invoice. |
| `futureBookings[].discountDetails.discountSalesInvoiceIds` | array of integer | The distinct sales invoice ids recorded against this group's discount usage entries — the invoices the discount itself was filed against. The response does not say how these relate to `paymentInfo.salesInvoiceId`, so do not assume they are the same invoices. |
| `futureBookings[].discountDetails.promotionCodes` | array of string | The distinct promotion codes applied across the group, with blank entries dropped. This can be an empty array while the amount fields are populated, where the underlying records carry no code. |

## Sample response

```json title="200 OK"
{
  "bookingHistory": [
    {
      "bookings": [
        {
          "bookingId": 12345,
          "bookingRef": 1,
          "slotReference": "string",
          "name": "string",
          "keyBookeeMemberId": 12345,
          "activityId": "string",
          "activityName": "string",
          "activityDescription": "string",
          "dateTime": "2026-08-14T19:00:00Z",
          "siteId": "string",
          "location": {
            "id": "string",
            "description": "string",
            "translations": [
              {
                "type": "string",
                "culture": "string",
                "text": "string"
              }
            ]
          },
          "attendedInCentre": true,
          "attendedVirtually": true,
          "keyBookeeIncludedInd": true,
          "paymentInfo": {
            "salesInvoiceId": 12345,
            "salesInvoiceTotal": 10.5,
            "salesInvoiceStatus": "string",
            "salesReceiptId": 12345
          },
          "translations": [
            {
              "type": "string",
              "culture": "string",
              "text": "string"
            }
          ],
          "bookeePriceLevels": [
            {
              "id": "string",
              "description": "string",
              "count": 1,
              "price": 10.5,
              "translations": [
                {
                  "type": "string",
                  "culture": "string",
                  "text": "string"
                }
              ]
            }
          ],
          "keyBookeeBookingInfo": {
            "priceLevelId": "string",
            "price": 10.5
          }
        }
      ],
      "discountDetails": {
        "originalPrice": 10.5,
        "discountedPrice": 10.5,
        "discountAmount": 10.5,
        "discountSalesInvoiceIds": [
          12345
        ],
        "promotionCodes": [
          "string"
        ]
      }
    }
  ],
  "futureBookings": [
    {
      "bookings": [
        {
          "bookingUuid": "b7f3c1e2-9a44-4f2b-8c17-0d5e9a1b6c33",
          "bookingId": 12345,
          "bookingRef": 1,
          "slotReference": "string",
          "name": "string",
          "keyBookeeMemberId": 12345,
          "activityId": "string",
          "activityName": "string",
          "activityDescription": "string",
          "imageUrl": "string",
          "dateTime": "2026-08-14T19:00:00Z",
          "endDateTime": "2026-08-14T19:00:00Z",
          "siteId": "string",
          "siteName": "string",
          "siteTranslations": [
            {
              "type": "string",
              "culture": "string",
              "text": "string"
            }
          ],
          "isRefundable": true,
          "keyBookeeIncludedInd": true,
          "location": {
            "id": "string",
            "description": "string",
            "translations": [
              {
                "type": "string",
                "culture": "string",
                "text": "string"
              }
            ]
          },
          "inCentre": {
            "bookingConfirmed": true,
            "onAlertList": true
          },
          "virtual": {
            "bookingConfirmed": true,
            "onAlertList": true
          },
          "paymentInfo": {
            "salesInvoiceId": 12345,
            "salesInvoiceTotal": 10.5,
            "salesInvoiceStatus": "string",
            "salesReceiptId": 12345
          },
          "translations": [
            {
              "type": "string",
              "culture": "string",
              "text": "string"
            }
          ],
          "bookeePriceLevels": [
            {
              "id": "string",
              "description": "string",
              "count": 1,
              "price": 10.5,
              "translations": [
                {
                  "type": "string",
                  "culture": "string",
                  "text": "string"
                }
              ]
            }
          ],
          "keyBookeeBookingInfo": {
            "priceLevelId": "string",
            "price": 10.5
          }
        }
      ],
      "discountDetails": {
        "originalPrice": 10.5,
        "discountedPrice": 10.5,
        "discountAmount": 10.5,
        "discountSalesInvoiceIds": [
          12345
        ],
        "promotionCodes": [
          "string"
        ]
      }
    }
  ]
}
```

## Errors

| Status | Meaning | When |
|--------|---------|------|
| `400` | Bad request | Two causes. Both `includeBookingHistory` and `includeFutureBookings` were sent as `false` — the body reads `You must include either booking history or future bookings in the request`. Or `memberId` is `0` or negative, giving `{memberId} is an invalid member ID`. A `400` from one of the lookups this call makes is also passed straight through, with a body naming the part that failed and quoting its response. |
| `404` | Not found | The member does not exist, or one of the lookups this call makes could not find them. The status and body are relayed verbatim from whichever lookup failed, so the body names it — for example `Booking History Service returned 404: Member ID {id} does not exist` or `Member Link Service returned 404: …`. Nothing partial is returned: a failure on any one of the fan-out calls aborts the whole response. |
| `500` | Internal server error | An unexpected failure while resolving links, fetching bookings or aggregating them — and also the catch-all for any failure status other than `400` or `404` from one of the lookups this call makes, so a `403` or `409` from one of them surfaces here as a `500` with an empty body. |
| `503` | Service unavailable | Part of the platform this call depends on could not be reached at all. The body reads `At least one of the services is unavailable.` This is the transient case — retry it, unlike a `400`. |
