Skip to main content

Booking groups API

A booking group ties several bookings together into one event with one price and one payment. For example, a company team day might include mini golf at 14:00 and laser tag at 15:30. Each activity is a normal booking; the group coordinates them.

Groups are usually created from an event package. Staff can also build a custom group activity by activity.

For how staff work with groups in the admin UI, see Booking groups.

Customer endpoints​

These endpoints need a signed-in customer (Authorization: Bearer <token>) and the x-tenant-id header.

MethodPathDescription
POST/api/v1/booking-groups/holdCreate a group and hold every activity at once.
GET/api/v1/booking-groups/{id}Get a group with its bookings and totals.
POST/api/v1/booking-groups/{id}/confirmStart payment for a held group.

Create a group hold​

POST /api/v1/booking-groups/hold
{
"packageId": "pkg_team_day",
"groupType": "PACKAGE",
"partySize": 20,
"customerName": "Example Oy",
"customerEmail": "events@example.com",
"customerPhone": "+358401234567",
"slotSelections": [
{ "productId": "p_golf", "siteId": "s_center", "slotId": "sl_1400" },
{ "productId": "p_laser", "siteId": "s_center", "slotId": "sl_1530" }
]
}
FieldRequiredDescription
groupTypeYesPACKAGE or CUSTOM.
partySizeYesGroup size, 1–500.
packageIdNoThe event package the group is built from.
customerNameNoCustomer or company name.
customerEmailNoWhere confirmations are sent.
customerPhoneNoCustomer phone number.
slotSelectionsNoOne entry per activity: productId, siteId, slotId, optional partySize.

All activities are held together with the same expiry. If any slot cannot be held, nothing is held.

The response is the group, with status: "HOLD", holdExpiresAt and a bookings list:

{
"id": "grp_123",
"reference": "GRP-20261015-001",
"status": "HOLD",
"groupType": "PACKAGE",
"partySize": 20,
"currency": "EUR",
"holdExpiresAt": "2026-10-15T11:15:00.000Z",
"bookings": [
{
"bookingId": "bk_001",
"status": "hold",
"slotId": "sl_1400",
"startsAt": "2026-10-15T11:00:00.000Z",
"endsAt": "2026-10-15T12:00:00.000Z"
}
]
}

Confirm a group​

POST /api/v1/booking-groups/{id}/confirm
{ "paymentMethod": "STRIPE" }

The group must be in HOLD and the hold must not have expired. The response is the group plus a stripeClientSecret. Complete the payment with Stripe on your page. The bookings stay on hold until Stripe reports the payment, then they are all confirmed together.

This endpoint accepts only STRIPE, with no customTotal or customPricingReason. Anything else gets 403 with code GROUP_CONFIRM_METHOD_NOT_ALLOWED and the bookings stay on hold. Invoiced, complimentary and custom-priced groups are confirmed by staff through the admin endpoint below.

Admin endpoints​

Admin endpoints need a staff token with the manage_bookings permission.

MethodPathDescription
GET/api/v1/admin/booking-groupsList groups. Filters: status, customerEmail.
POST/api/v1/admin/booking-groupsCreate a group in DRAFT. Same body as the customer hold.
GET/api/v1/admin/booking-groups/{id}Get a group.
PATCH/api/v1/admin/booking-groups/{id}Update customer details and internalNotes.
DELETE/api/v1/admin/booking-groups/{id}Delete a draft group.
POST/api/v1/admin/booking-groups/{id}/bookingsAdd an activity to a draft group.
DELETE/api/v1/admin/booking-groups/{id}/bookings/{bookingId}Remove an activity from a draft group.
POST/api/v1/admin/booking-groups/{id}/calculate-pricingPrice the group and split the total across its bookings.
POST/api/v1/admin/booking-groups/{id}/holdHold every activity at once, with one expiry.
POST/api/v1/admin/booking-groups/{id}/confirmConfirm the group.
POST/api/v1/admin/booking-groups/{id}/cancelCancel every active booking in the group. Body: { "reason" }.

Add an activity​

{
"productId": "p_golf",
"siteId": "s_center",
"slotId": "sl_1400",
"partySize": 20
}

The product must belong to the site, and the slot must belong to the product.

Calculate pricing​

Returns the group total and how it is split:

{
"grandTotal": 1100.0,
"subtotal": 893.5,
"taxTotal": 206.5,
"currency": "EUR",
"pricingMethod": "BUNDLE_PER_PERSON",
"allocations": [
{
"bookingId": "bk_001",
"allocatedSubtotal": 446.75,
"taxAmount": 103.25,
"allocatedTotal": 550.0
}
]
}

pricingMethod is FIXED_PRICE, BUNDLE_PER_PERSON or INDIVIDUAL_SUM, depending on the package's pricing.

Confirm (staff)​

{
"paymentMethod": "INVOICE",
"customTotal": 900.0,
"customPricingReason": "Returning customer discount"
}
FieldRequiredDescription
paymentMethodYesSTRIPE, PAYTRAIL, INVOICE or COMP.
customTotalNoOverride the total.
customPricingReasonWith customTotalWhy the total was changed. Saved in the group notes.
  • INVOICE: all bookings are confirmed now; the payment is recorded as pending.
  • COMP: all bookings are confirmed at no charge.
  • STRIPE: returns a stripeClientSecret, as in the customer flow.

Group status​

StatusMeaning
DRAFTCreated; activities are still being added.
HOLDEvery activity is held.
CONFIRMEDPaid or approved; every booking is confirmed.
PARTIALLY_CANCELEDSome bookings are cancelled, others are still active.
CANCELEDEvery booking is cancelled.
COMPLETEDEvery booking has finished.
EXPIREDThe hold ran out before the group was confirmed.

The group status follows its bookings and updates when any of them changes.