Event packages API
An event package is a ready-made bundle of activities, for example "Team day: mini golf + laser tag". Customers book a package as a booking group: one hold, one price, one payment.
A package has:
- a list of activities (a product at a site, in order)
- party size limits
- a price: a fixed total, a per-person bundle price, or the sum of the activities' own prices
- an active flag and a public flag
For how staff set up packages in the admin UI, see Event packages.
Customer endpoints
These endpoints need the x-tenant-id header and, at the moment, a signed-in user (Authorization: Bearer <token>).
| Method | Path | Description |
|---|---|---|
GET | /api/v1/event-packages | List active, public packages. |
GET | /api/v1/event-packages/{slug} | Get one active package by its slug. |
Example response item:
{
"id": "pkg_team_day",
"name": "Team day",
"description": "Mini golf and laser tag for your team",
"slug": "team-day",
"minPartySize": 10,
"maxPartySize": 40,
"bundlePricePerPerson": 55.0,
"fixedPrice": null,
"imageUrl": null,
"isActive": true,
"isPublic": true,
"holdTtlMinutes": null,
"items": [
{
"id": "item_1",
"productId": "p_golf",
"siteId": "s_center",
"sortOrder": 0,
"durationOverride": null,
"isRequired": true
}
]
}
The slug lookup returns any active package, public or not, so you can share a private package by its link.
Admin endpoints
Admin endpoints need a staff token with the manage_catalog permission.
| Method | Path | Description |
|---|---|---|
GET | /api/v1/admin/event-packages | List all packages, including inactive and private ones. |
POST | /api/v1/admin/event-packages | Create a package. |
GET | /api/v1/admin/event-packages/{id} | Get a package. |
PATCH | /api/v1/admin/event-packages/{id} | Update a package. Send only the fields you change. |
DELETE | /api/v1/admin/event-packages/{id} | Delete a package. Existing booking groups are not affected. |
POST | /api/v1/admin/event-packages/{id}/items | Add an activity. |
PATCH | /api/v1/admin/event-packages/{id}/items/{itemId} | Change an activity's order, duration or required flag. |
DELETE | /api/v1/admin/event-packages/{id}/items/{itemId} | Remove an activity. |
Create a package
{
"name": "Team day",
"description": "Mini golf and laser tag for your team",
"minPartySize": 10,
"maxPartySize": 40,
"bundlePricePerPerson": 55.0,
"isActive": true,
"isPublic": false,
"holdTtlMinutes": 30,
"items": [
{ "productId": "p_golf", "siteId": "s_center", "sortOrder": 0 },
{ "productId": "p_laser", "siteId": "s_center", "sortOrder": 1 }
]
}
| Field | Required | Description |
|---|---|---|
name | Yes | Display name. The slug is made from it and must be unique. |
minPartySize | Yes | Smallest group, at least 1. |
maxPartySize | Yes | Largest group, up to 500. |
description | No | Marketing text. |
bundlePricePerPerson | No | Price per person for the whole package. |
fixedPrice | No | Total price, whatever the party size. |
imageUrl | No | Package image. |
isActive | No | Default true. |
isPublic | No | Listed to customers. Default false. |
holdTtlMinutes | No | How long a group hold lasts, 5–120 minutes. |
items | No | Activities. You can also add them later. |
Each item takes productId, siteId, and optionally sortOrder, durationOverride (minutes) and isRequired. The product must belong to the site, or the request fails with 400.
If you change the name, the slug changes too, and old links stop working.
Pricing
When a group is priced, the first rule that applies wins:
fixedPrice: the group total.bundlePricePerPerson: multiplied by the party size.- Neither set: the sum of each activity's own price.
The total is then split across the group's bookings in proportion to each activity's own price. Partial refunds use this split.