# List all available sessions (Version 2)

`GET /api/availability/v2/sessions`

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

HTML version: [List all available sessions (Version 2)](https://support.gladstonesoftware.cloud/reference/bookings/list-all-available-sessions-version-2)

## Overview

Lists the bookable sessions for one or more activities, day by day, together with how much space is left in each and the window during which each may be booked. This is the starting point of the booking flow: take a `slotReferences` value from here, price it with [Retrieve pricing information for different members](retrieve-pricing-information-for-different-members.md), then create a lease and confirm the booking.

There is no member context, so booking windows are worked out from each site's default price level rather than anyone's membership. Use [List all available sessions for a specific member (Version 2)](list-all-available-sessions-for-a-specific-member-version-2.md) when you know who is booking, because a member's price level can open a slot earlier than the default.

Two things about the shape of the response catch people out. It is **flattened by date**: one array entry per activity per day, with the day in `date`, so the same `id` appears many times and days with no slots are missing rather than empty. And it is ordered by the start time of the first slot, then by name — not grouped by activity or site.

### Choosing between version 1 and version 2

Version 2 is the one to build against for anything new. It differs in four ways that matter:

- **Filtering.** Version 1 accepts only `activityIds`, and requires it. Version 2 accepts `activityIds`, `tagIds` or `activityGroupIds` — any one of the three will do — so you can search a category or a tag without first resolving it to a list of activities. `activityIds` and `tagIds` cannot be combined.
- **Date range.** Version 1 always runs from `dateFrom` to the tenant's configured look-ahead period. Version 2 adds `dateTo` so you can ask for a shorter window.
- **Locations.** Version 1 returns one entry per resource product, so an activity that needs two courts together appears as two locations repeating the same slots. Version 2 returns one entry per *combination* of resources, with the individual products listed under `locationDetails`.
- **Extra data.** Version 2 adds `tagGroups` and `bookingDiscountTypes` to each entry.

Slot-level data is identical between the versions.

## Endpoint

`GET {base_url}/api/availability/v2/sessions`

`{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 |
|------|------|----------|-------------|
| `activityIds` | string | No | Comma-separated activity ids to search. Optional in version 2, but **at least one of `activityIds`, `tagIds` or `activityGroupIds` must be supplied** or the request fails with `400 Either ActivityIds, TagIds, or ActivityGroupIds must be provided.` Cannot be combined with `tagIds`. Any id that does not exist fails the whole request with `400`. |
| `tagIds` | string | No | Comma-separated tag ids, resolved to the activities carrying any of them. Take the ids from `tagGroups[].tags[].id` on an earlier response. **Mutually exclusive with `activityIds`** — sending both fails with `400 Cannot filter by both activity ID and tag ID. Please only use one.` Non-numeric entries are silently discarded rather than rejected, so a typo narrows your search instead of erroring. A tag id that does not exist fails with `400`. |
| `siteIds` | string | No | Comma-separated site ids to restrict the search to. **At most five**; a sixth fails with `400 A maximum of 5 SiteIds can be specified`. Omit to search every site the matched activities belong to. |
| `webBookableOnly` | boolean | No | Restricts the results to activities flagged for online booking. **Defaults to `true`**, so by default you see only what a caller could book online — pass `false` explicitly to include activities that are bookable only at the centre. |
| `dateFrom` | string (date-time) | No | The first date to return sessions for. Defaults to now (UTC) when omitted, and **must not be earlier than today** — a past date fails with `400 DateFrom cannot be in the past. Please enter a valid date.` |
| `dateTo` | string (date-time) | No | The last date to return sessions for, inclusive to end of day. New in version 2, and the only way to override the tenant's configured look-ahead period, which is used when you omit it. Must not be in the past, must not be before `dateFrom`, and **must not equal `dateFrom`** — asking for a single day this way fails with `400 DateFrom cannot be the same as DateTo. Please enter a valid date.`, so use the day after for a one-day window. |
| `locationId` | string | No | Narrows the results to one resource product — a single court, lane or room. Take the value from `locations[].locationDetails[].locationId` on an earlier response. An id that is not a valid resource fails with `400 Invalid location ID: {locationId}`. |
| `activityGroupIds` | string | No | Comma-separated activity group ids, matching `activityGroupId` in the response — useful for pulling a whole category such as racquet sports. New in version 2. Can be combined with `activityIds` or `tagIds`, and counts towards the requirement that at least one of the three filters is supplied. |

## Sample request

```bash title="cURL"
curl -X GET '{base_url}/api/availability/v2/sessions' \
  -H 'Authorization: Bearer {accessToken}' \
  -H 'X-Use-SSO: true'
```

## Response fields

| Field | Type | Description |
|-------|------|-------------|
| `[].activityGroupId` | string | The activity group the activity belongs to — the category tenants use to organise their timetable, such as racquet sports. Null when the activity is not in a group. |
| `[].activityGroupDescription` | string | The display name of that activity group. |
| `[].id` | string | The activity these sessions belong to. Because the response is split by date, the same `id` appears once for every date that has slots — `id` alone does not identify an array entry, `id` plus `date` does. |
| `[].name` | string | The activity's short name, suitable for a timetable row. |
| `[].description` | string | The activity's long description, for a detail page. |
| `[].date` | string | The calendar date the slots in this entry fall on, as `yyyy-MM-dd`. One entry is produced per activity per date, and dates with no slots are dropped entirely rather than returned empty. |
| `[].imageUrl` | string | Image for the activity, ready to use as-is. A relative image reference is expanded to a full public CDN URL; a value already beginning `http` is returned unchanged. Null when the activity has no image. |
| `[].inCentre` | boolean | Whether the activity can be attended in person. Both this and `virtual` are `true` for an activity configured as mixed, so they are not mutually exclusive — check both before deciding which of `slotReferences.inCentre` and `slotReferences.virtual` to use. |
| `[].virtual` | boolean | Whether the activity can be attended remotely. See `inCentre` — a mixed activity sets both. |
| `[].siteId` | string | The site the activity runs at. Useful when you queried several sites at once, since the array interleaves them. |
| `[].webBookable` | boolean | Whether the activity is flagged for online booking. With the default `webBookableOnly=true` this is always `true`, since anything else has already been filtered out; it is only informative when you pass `webBookableOnly=false`. |
| `[].webComments` | string | Free-text note the tenant configured for online display against this activity — kit to bring, entry instructions, and so on. Null when none is set. |
| `[].capacity` | object | The activity's configured maximum party sizes. This is the ceiling, not what is left — for remaining space use `locations[].slots[].availability`. |
| `[].capacity.maxInCentreBookees` | integer (int32) | The activity's configured maximum number of in-centre bookees — **except** when the entry has exactly one slot, in which case it is replaced by that slot's own capacity. Reservations can carry a per-booking capacity, and this is how version 2 surfaces it. |
| `[].capacity.maxVirtualBookees` | integer (int32) | The activity's configured maximum number of virtual bookees — **except** when the entry has exactly one slot, in which case it is replaced by that slot's `availability.virtual`, which is always `0`. Single-slot entries therefore report `0` here regardless of configuration. |
| `[].typeInd` | string | What kind of activity this is, which governs how you book it. One of `partyactivity` (extra bookees allowed), `hostedreservation`, `activity` (a standard bookable resource such as a court), `course` (a class where all sessions are booked together) or `class`. Evaluated in that order, so a party activity reports `partyactivity` regardless of its other flags. |
| `[].slotCount` | integer (int32) | How many slots this entry contains, counted across every location. Always at least `1`: entries that would have no slots on the date are not returned. |
| `[].groupActivityDetails` | object | Whether extra people can be added to a booking of this activity. |
| `[].groupActivityDetails.isGroupActivity` | boolean | `true` when the activity is a party activity, meaning a booking may include additional bookees beyond the member. Only then will [Retrieve pricing information for different members](retrieve-pricing-information-for-different-members.md) accept an `additionalBookees` list; it rejects them otherwise. |
| `[].groupActivityDetails.priceLevels` | array of object | **Always empty**, so do not use this endpoint to discover which price levels a party activity offers — price the slot instead. |
| `[].groupActivityDetails.priceLevels[].id` | string | Never returned, as the containing array is always empty. |
| `[].groupActivityDetails.priceLevels[].description` | string | Never returned, as the containing array is always empty. |
| `[].bookingDiscountTypes` | array of object | Booking discounts that could apply to this activity on this date. Populated only when the booking discounts feature is enabled for the tenant and a discount's validity period covers the date; **null** rather than empty otherwise. This tells you a discount exists, not what it is worth — the amount comes from [Retrieve pricing information for different members](retrieve-pricing-information-for-different-members.md). Not present in version 1. |
| `[].bookingDiscountTypes[].discountType` | string | The name of the configured discount. Values are tenant configuration, so treat this as a label rather than a fixed set. Duplicates are removed. |
| `[].locations` | array of object | Where the sessions take place. Version 2 groups resources: one entry per *combination* of resource products that makes up a bookable place, rather than version 1's one entry per individual product. An activity requiring two courts together is one location here with two `locationDetails`, where version 1 would have shown two locations repeating the same slots. |
| `[].locations[].locationNameToDisplay` | string | The name to show for the location. Where several resource products are combined, this is the description of the first of them, not a concatenation — use `locationDetails` if you need to list them all. |
| `[].locations[].locationDetails` | array of object | The individual resource products making up this location. A single entry when one product is enough to book the slot; several when the activity requires them together. |
| `[].locations[].locationDetails[].locationId` | string | The resource product identifier. This is the value to pass as the `locationId` query parameter to narrow a later query to one court or room. |
| `[].locations[].locationDetails[].locationName` | string | The resource product's description, for display. |
| `[].locations[].slots` | array of object | The bookable occurrences at this location on `date`, ordered by start time. For a standard activity these are theoretical slots generated from the timetable; for a class they are the class's existing bookings. |
| `[].locations[].slots[].startTime` | string (date-time) | When the session starts, in UTC. Every slot in an entry starts on the entry's `date`. |
| `[].locations[].slots[].endTime` | string (date-time) | When the session ends, in UTC. For a generated activity slot this is one second before the next slot would begin, so expect times such as `10:59:59`. |
| `[].locations[].slots[].bookableFrom` | string (date-time) | The earliest moment this slot may be booked, in UTC, seconds trimmed. It comes from the advance booking period that applies to the caller — which for a member query depends on their price level, so the same slot can open earlier for one member than another. With no advance booking period it is simply today. Null is not expected. |
| `[].locations[].slots[].bookableUntil` | string (date-time) | The latest moment this slot may be booked, in UTC, seconds trimmed, derived from the tenant's booking cut-off configuration relative to the session's start or end. If misconfiguration would put it before `bookableFrom`, it is clamped to `bookableFrom`, leaving effectively no booking window. |
| `[].locations[].slots[].isBeforeTimetableSlotBookablePeriod` | boolean | `true` when `bookableFrom` is still in the future at the moment the response was built — the slot exists and has space, but is not open for booking yet. Show it as "available from" rather than bookable. Note that `status` can still read `Available`, so check this flag as well as `status`. |
| `[].locations[].slots[].availability` | object | Spaces left on this slot, as opposed to the activity's configured maximum in `capacity`. |
| `[].locations[].slots[].availability.inCentre` | integer (int32) | Spaces still free, calculated as the maximum bookees less those already booked. Unexpired booking leases held by other members count as taken, so this can fall without any booking being completed. Forced to `0` when an overlapping booking blocks the slot or the booking cut-off has passed. |
| `[].locations[].slots[].availability.virtual` | integer (int32) | **Always `0`.** Remaining virtual capacity is not calculated by this endpoint, for either generated or booking-derived slots. Do not read a fully-booked virtual session into this value. |
| `[].locations[].slots[].alertListEnabled` | boolean | Whether members may join an [alert list](../../guides/glossary.md) for this slot when it is full. Only ever `true` for slots derived from existing bookings — that is, classes and courses. Slots generated from an activity's timetable always report `false`, even where the activity has a waiting list configured. |
| `[].locations[].slots[].alertListCount` | integer (int32) | How many members are already queuing on the alert list. `0` on generated activity slots, which never carry alert-list data. |
| `[].locations[].slots[].status` | string (enum) | One of: `Available`, `Unavailable`. |
| `[].locations[].slots[].slotReferences` | object | The [slot references](../../guides/glossary.md) to quote back when pricing and booking this slot. Pick the member the field matching how they will attend. |
| `[].locations[].slots[].slotReferences.inCentre` | string | The reference for attending in person. **Null when the activity is virtual-only**, so check before using it. It is an opaque JSON string — pass it to pricing and lease creation exactly as received; do not parse or rebuild it. |
| `[].locations[].slots[].slotReferences.virtual` | string | The reference for attending remotely. **Null when the activity is in-centre only.** Treat it as opaque, as with `inCentre`. |
| `[].locations[].translations` | array of object | Translated forms of the location name, for tenants running more than one language. Empty when the location has no translations configured. |
| `[].locations[].translations[].type` | string | |
| `[].locations[].translations[].culture` | string | The culture the text belongs to, used to select the variant matching the member's language. |
| `[].locations[].translations[].text` | string | The translated text — display this instead of the untranslated name when it matches the member's culture. |
| `[].translations` | array of object | Translated forms of the activity's own text. Empty when none are configured. |
| `[].translations[].type` | string | |
| `[].translations[].culture` | string | The culture the text belongs to. |
| `[].translations[].text` | string | The translated text. |
| `[].activityGroupTranslations` | array of object | Translated forms of the activity group's text, separate from the activity's own. Empty when none are configured, or when the activity is not in a group. |
| `[].activityGroupTranslations[].type` | string | |
| `[].activityGroupTranslations[].culture` | string | The culture the text belongs to. |
| `[].activityGroupTranslations[].text` | string | The translated text. |
| `[].tagGroups` | array of object | The tags attached to this activity, arranged by the group they belong to — the metadata tenants use to categorise their timetable, such as intensity or suitability. Only in version 2, and the source of the ids you pass to the `tagIds` query parameter. |
| `[].tagGroups[].id` | integer (int32) | The tag group's identifier. Note that `tagIds` filters on tags, not tag groups, so this value cannot be used as a filter. |
| `[].tagGroups[].name` | string | The tag group's name, suitable as a heading above its tags. |
| `[].tagGroups[].tags` | array of object | The tags in this group that are attached to the activity. |
| `[].tagGroups[].tags[].id` | integer (int32) | The tag's identifier. Pass these, comma-separated, as `tagIds` to find every activity carrying any of them. |
| `[].tagGroups[].tags[].name` | string | The tag's name, for display. |
| `[].tagGroups[].tags[].translations` | array of object | Translated forms of the tag's name. Empty when none are configured. |
| `[].tagGroups[].tags[].translations[].type` | string | |
| `[].tagGroups[].tags[].translations[].culture` | string | The culture the text belongs to. |
| `[].tagGroups[].tags[].translations[].text` | string | The translated text. |
| `[].tagGroups[].translations` | array of object | Translated forms of the tag group's name, separate from its tags'. Empty when none are configured. |
| `[].tagGroups[].translations[].type` | string | |
| `[].tagGroups[].translations[].culture` | string | The culture the text belongs to. |
| `[].tagGroups[].translations[].text` | string | The translated text. |

## Sample response

```json title="200 OK"
[
  {
    "activityGroupId": "string",
    "activityGroupDescription": "string",
    "id": "string",
    "name": "string",
    "description": "string",
    "date": "string",
    "imageUrl": "string",
    "inCentre": true,
    "virtual": true,
    "siteId": "string",
    "webBookable": true,
    "webComments": "string",
    "capacity": {
      "maxInCentreBookees": 1,
      "maxVirtualBookees": 1
    },
    "typeInd": "string",
    "slotCount": 1,
    "groupActivityDetails": {
      "isGroupActivity": true,
      "priceLevels": [
        {
          "id": "string",
          "description": "string"
        }
      ]
    },
    "bookingDiscountTypes": [
      {
        "discountType": "string"
      }
    ],
    "locations": [
      {
        "locationNameToDisplay": "string",
        "locationDetails": [
          {
            "locationId": "string",
            "locationName": "string"
          }
        ],
        "slots": [
          {
            "startTime": "2026-08-14T19:00:00Z",
            "endTime": "2026-08-14T19:00:00Z",
            "bookableFrom": "2026-08-14T19:00:00Z",
            "bookableUntil": "2026-08-14T19:00:00Z",
            "isBeforeTimetableSlotBookablePeriod": true,
            "availability": {
              "inCentre": 1,
              "virtual": 1
            },
            "alertListEnabled": true,
            "alertListCount": 1,
            "status": "Available",
            "slotReferences": {
              "inCentre": "string",
              "virtual": "string"
            }
          }
        ],
        "translations": [
          {
            "type": "string",
            "culture": "string",
            "text": "string"
          }
        ]
      }
    ],
    "translations": [
      {
        "type": "string",
        "culture": "string",
        "text": "string"
      }
    ],
    "activityGroupTranslations": [
      {
        "type": "string",
        "culture": "string",
        "text": "string"
      }
    ],
    "tagGroups": [
      {
        "id": 12345,
        "name": "string",
        "tags": [
          {
            "id": 12345,
            "name": "string",
            "translations": [
              {
                "type": "string",
                "culture": "string",
                "text": "string"
              }
            ]
          }
        ],
        "translations": [
          {
            "type": "string",
            "culture": "string",
            "text": "string"
          }
        ]
      }
    ]
  }
]
```

## Errors

| Status | Meaning | When |
|--------|---------|------|
| `400` | Bad request | None of `activityIds`, `tagIds` or `activityGroupIds` was supplied (`Either ActivityIds, TagIds, or ActivityGroupIds must be provided.`); both `activityIds` and `tagIds` were supplied (`Cannot filter by both activity ID and tag ID. Please only use one.`); `dateFrom` is in the past, `dateTo` is in the past, `dateTo` is before `dateFrom`, or `dateTo` equals `dateFrom`; `locationId` is not a valid resource (`Invalid location ID: {locationId}`); more than five site ids were given (`A maximum of 5 SiteIds can be specified`); a tag id does not exist (`One or more TagIds in the list […] doesn't exist.`); or one of the sites or activities you named does not exist. |
| `500` | Internal server error | Any failure that is not a validation error. The response is a bare `500` with no body, so there is nothing in the response to act on. |
