List all member participation records
GET /api/participation
Success: 200 OK
- Products: Operator (
FOH), Integrations (INTEGRATIONS) (any one of these products, not all) - Features: TRP Integration (
TRP), DataHub (4Global) Integration (DATAHUB), Front of House Generic Access (FOHGENERIC) (any one of these features, not all)
Overview
Returns a paged, cross-site record of member attendance at your sites in a date range: the classes and other booked activities members were marked as attending, their entry swipes, and the sales that count as participation.
A participation record is one occurrence of a member using the operator's facilities. The endpoint merges three separate sources into a single stream, and the type field tells you which one produced each record:
Booking— a member's place on a booked activity (a class, a court, a session). By default only places marked as attended are returned.Swipe— a member presenting their card or credential at an entry point, recorded by access control. There is no booking behind it.ProductSale— a sold line that counts as participation, such as a casual entry or an item bought against a booking, taken from the member's invoice.
Each record carries the member, the site, a timestamp and exactly one populated block inside details matching its type. This is the endpoint to poll when you are building attendance reporting, usage analytics or a data warehouse feed — it is a bulk export shaped for reading, not a per-member lookup, and there is no member filter.
Scope and paging are the two things to get right. You must name between one and five sites; each of the three sources has its own cursor, which is why the continuation token carries three ids at once. Do not construct that token yourself — take links.next from the previous response and call it verbatim. Note that links.next is returned on every response, including the last one, so it is not a signal that more data exists: stop when data comes back empty.
Watch the interaction between limit and the three sources. The limit you send is divided by three, so each source returns at most a third of it. A limit of 100 yields at most 33 bookings, 33 swipes and 33 product sales, and records are grouped by source within the page rather than sorted by time — sort client-side if order matters to you.
Endpoint
GET {base_url}/api/participation
{base_url} is the environment URL issued to your integration. See Authenticating with Gladstone APIs.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
siteIds | string | Yes | Comma-separated list of site ids to report on, for example siteIds=SITE1,SITE2. Despite being a query parameter it is not optional: omitting it, or naming more than five sites, fails with At least one but no more than 5 siteIds can be specified. Every id must exist or the whole call fails with 404. |
fromDate | string (date-time) | No | Inclusive lower bound on the record time — booking start time, swipe time or invoice date depending on the record. Defaults to midnight at the start of the current UTC day when omitted, so leaving it out gives you today only, not everything. |
toDate | string (date-time) | No | Inclusive upper bound on the record time. Defaults to 23:59:59 on the current UTC day. If you pass a value with a zero time component, it is moved to 23:59:59 on that day, so a date-only toDate always covers the whole day. Must not be earlier than fromDate. |
continuation-token | string | No | Cursor for the next page. Omit it on the first call; afterwards take the whole links.next URL from the previous response rather than assembling this yourself. It carries three cursors at once in the form attendance-id:{n}_invoice-id:{n}_swipe-id:{n} — underscore-separated key:value pairs whose values must be integers. Any element that is not a key:value pair, or whose value will not convert to an integer, fails with 400. Unrecognised keys are ignored silently, which will quietly restart that source from the beginning. |
limit | integer (int32) | No | Maximum records per page, defaulting to 100. The value is divided by three, so each of the three sources returns at most a third of it and a page rarely holds exactly limit records. Your original value, not the divided one, is echoed into links.next. |
attendedBookingsOnly | boolean | No | Whether to restrict the booking source to places actually marked as attended. Defaults to true, which is why bookings a member did not turn up to are missing unless you send false. Has no effect on swipes or product sales. |
Sample request
curl -X GET '{base_url}/api/participation' \
-H 'Authorization: Bearer {accessToken}' \
-H 'X-Use-SSO: true'Response fields
| Field | Type | Description |
|---|---|---|
data | array of object | The page of participation records, mixed across all three sources. Records are grouped by source — product sales, then swipes, then bookings — not sorted by time. An empty array means you have reached the end of the range. |
data[].memberId | integer (int32) | The member this record belongs to — the member whose attendance, swipe or sale line this is. There is no way to filter the feed by member; filter client-side. |
data[].siteId | string | The site the participation happened at — one of the ids you passed in siteIds. For bookings this is the site of the booked activity; for swipes, the site of the entry point; for product sales, the site the sale was recorded against. |
data[].time | string (date-time) | When the participation happened. The meaning follows type: the booking's start time for Booking, the swipe timestamp for Swipe, and the date the invoice was raised for ProductSale. Records are not returned in time order. |
data[].type | string | Which source produced this record, and therefore which block of details is populated. One of Booking, Swipe or ProductSale. |
data[].details | object | Holds the source-specific payload. Exactly one of swipe, booking and productSale is populated, matching type; the other two are absent. Switch on type rather than probing for whichever key is present. |
data[].details.swipe | object | Populated when type is Swipe. A swipe carries no activity or price information — it records only that the member passed through an entry point at time. |
data[].details.swipe.entryPointId | string | The id of the entry point — the turnstile, door or reader — the member was admitted through, as configured in access control. |
data[].details.booking | object | Populated when type is Booking. Describes the member's place on a booked activity, plus the activity's own configuration at the time of reading. |
data[].details.booking.activityId | string | The id of the booked activity. Records sharing an activityId describe the same activity, so this is the key to group or join on. |
data[].details.booking.activityName | string | The activity's description in the system's base language. Use translations for other languages. |
data[].details.booking.attended | boolean | Whether the member was marked as having attended. This is always true unless you called with attendedBookingsOnly=false, which is the only way unattended places enter the feed. |
data[].details.booking.locationGroupId | string | The id of the activity group the activity belongs to — the operator's grouping of related activities, not a physical place. For the physical resources see locations. |
data[].details.booking.startTime | string (date-time) | The booked activity's start time. Identical to the record's time for booking records. |
data[].details.booking.endTime | string (date-time) | The booked activity's end time. |
data[].details.booking.locations | array of object | The resources the activity is made up of — courts, rooms, lanes and similar. These come from the activity's configuration, so every booking of the same activity reports the same list; it is not a per-booking allocation. Empty when the activity has no resources configured. |
data[].details.booking.locations[].id | string | The id of the resource. |
data[].details.booking.locations[].name | string | The resource's description in the system's base language. |
data[].details.booking.locations[].translations | array of object | Translated forms of the resource's name, one entry per configured culture. Empty when none are configured. |
data[].details.booking.locations[].translations[].type | string | |
data[].details.booking.locations[].translations[].culture | string | The culture code this translation is for, matching the codes configured for the operator — for example en-GB or cy-GB. |
data[].details.booking.locations[].translations[].text | string | The translated resource name for that culture. |
data[].details.booking.capacity | object | The activity's maximum places, split by delivery mode. This is capacity, not how many were taken. |
data[].details.booking.capacity.inCentre | integer (int32) | Maximum in-centre places: the booking's own limit where one is set, otherwise the activity's. 0 when the activity is virtual-only, in which case there are no in-centre places at all. |
data[].details.booking.capacity.virtual | integer (int32) | Maximum virtual places. 0 when the activity is in-centre only. |
data[].details.booking.inCentre | boolean | Whether the activity can be attended in person. true for in-centre and mixed activities, false only for virtual-only ones. Mixed activities report true for both inCentre and virtual. |
data[].details.booking.virtual | boolean | Whether the activity can be attended remotely. true for virtual and mixed activities, false only for in-centre-only ones. |
data[].details.booking.bookingId | integer (int32) | The id of the booking this place belongs to. Several participation records can share one bookingId when a booking covers more than one member. |
data[].details.booking.usage | integer (int32) | How many people this single record should count as when you total participation. Normally 1, but for activities configured as party bookings it is the party size, so one record can represent a whole group. Sum this rather than counting records. |
data[].details.booking.translations | array of object | Translated forms of activityName, one entry per configured culture. Empty when none are configured. |
data[].details.booking.translations[].type | string | |
data[].details.booking.translations[].culture | string | The culture code this translation is for, matching the codes configured for the operator — for example en-GB or cy-GB. |
data[].details.booking.translations[].text | string | The translated activity name for that culture — display this in place of activityName. |
data[].details.productSale | object | Populated when type is ProductSale. Describes a sold line that counts as participation — typically a casual entry or an item sold against a booking — taken from the invoice raised for the member. |
data[].details.productSale.productId | string | The id of the product sold. |
data[].details.productSale.productName | string | The product's description in the system's base language. |
data[].details.productSale.quantity | number (double) | How many units were sold on this line. Decimal, so fractional quantities are possible. This is a sales quantity, not a participation count — for participation use usage. |
data[].details.productSale.usage | integer (int32) | How many people this sale should count as when totalling participation. Falls back to 1 when the product has no usage configured against it. |
data[].details.productSale.translations | array of object | Translated forms of productName, one entry per configured culture. Empty when none are configured. |
data[].details.productSale.translations[].type | string | |
data[].details.productSale.translations[].culture | string | The culture code this translation is for, matching the codes configured for the operator — for example en-GB or cy-GB. |
data[].details.productSale.translations[].text | string | The translated product name for that culture. |
links | object | Paging links for this response. |
links.next | string | A ready-made absolute URL for the next page, with your siteIds, dates, attendedBookingsOnly, original limit and the advanced continuation token already applied. Call it as-is. Two traps: it is present on every response, including the last, so it does not mean more data exists — stop when data is empty; and the dates in it are rewritten as yyyy-MM-dd, so any time component you originally sent is dropped from page two onwards. |
Sample response
{
"data": [
{
"memberId": 12345,
"siteId": "string",
"time": "2026-08-14T19:00:00Z",
"type": "string",
"details": {
"swipe": {
"entryPointId": "string"
},
"booking": {
"activityId": "string",
"activityName": "string",
"attended": true,
"locationGroupId": "string",
"startTime": "2026-08-14T19:00:00Z",
"endTime": "2026-08-14T19:00:00Z",
"locations": [
{
"id": "string",
"name": "string",
"translations": [
{
"type": "string",
"culture": "string",
"text": "string"
}
]
}
],
"capacity": {
"inCentre": 1,
"virtual": 1
},
"inCentre": true,
"virtual": true,
"bookingId": 12345,
"usage": 1,
"translations": [
{
"type": "string",
"culture": "string",
"text": "string"
}
]
},
"productSale": {
"productId": "string",
"productName": "string",
"quantity": 10.5,
"usage": 1,
"translations": [
{
"type": "string",
"culture": "string",
"text": "string"
}
]
}
}
}
],
"links": {
"next": "string"
}
}Errors
| Status | Meaning | When |
|---|---|---|
400 | Bad request | One of: The from date can not be after the to date; At least one but no more than 5 siteIds can be specified (also raised when siteIds is missing entirely); Invalid element "{element}" in token "{token}" when a continuation token segment is not a key:value pair; or Invalid value "{value}" for token value "{key}" when a token value is not an integer. |
404 | Not found | One or more site Ids do not exist: {siteIds} — every id in siteIds is checked before any records are returned, and one unknown id fails the whole call rather than being skipped. The message lists the ids you sent, not the offending one. |
500 | Internal server error | An unexpected server-side failure. Also returned, with an empty body, when the request does not arrive through your issued base URL — the endpoint needs that route to build links.next, so calling it by any other address produces this. |