Manage passes
List passesโ
GET /v1/passes
Scope store:read. Query: page, per_page, who_can_buy (everyone / staff_only / off),
for_sale (true / false).
{
"data": [
{
"id": 4,
"name": "Season pass",
"description": null,
"price": 150.0,
"billing": "once",
"discount_percent": null,
"visits": 10,
"unlimited_visits": false,
"valid_from": "2026-07-01",
"valid_to": "2026-08-31",
"valid_for": null,
"valid_for_unit": null,
"validity": "Jul 1, 2026 - Aug 31, 2026",
"experience_applies_to": "experiences",
"rental_applies_to": "none",
"covers": "items",
"experience_ids": [12],
"product_ids": [],
"experience_category_ids": [],
"product_category_ids": [],
"who_can_buy": "everyone",
"for_sale": true,
"sold_online": true,
"created_at": "2026-06-01T08:00:00+00:00",
"updated_at": "2026-06-01T08:00:00+00:00"
}
],
"meta": { "current_page": 1, "per_page": 15, "total": 1, "total_pages": 1 }
}
validity is how long the pass works, in words, as the booking page shows it: the dates, or
Valid for 12 months from purchase. It is null for a membership.
Get a passโ
GET /v1/passes/{id}
Scope store:read. Returns one pass in the shape above, or 404.
Create a passโ
POST /v1/passes
Scope store:write.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | What customers see, up to 120 characters |
price | number | yes | In the shop's currency. For a membership, the monthly fee |
billing | string | no | once (default): a season pass, paid for once. monthly: a membership, charged every month; it has no visits or dates, so those fields are ignored |
discount_percent | number | with monthly | 1 to 100. The member discount, taken off every line the membership covers |
visits | integer | no | 1 to 1000. Leave out or send null for unlimited |
valid_from | date Y-m-d | no | First day it works |
valid_to | date Y-m-d | no | Last day it works, on or after valid_from |
valid_for | integer | no | Instead of dates: how long each pass works from the day it is bought. Up to 120 months or 3660 days. Not with valid_from / valid_to |
valid_for_unit | string | no | months or days. Left out, a pass keeps the unit it has; a new one uses months |
experience_applies_to | string | no | Which experiences it covers: all, categories, experiences or none |
rental_applies_to | string | no | Which rental items it covers: all, categories, products or none |
experience_category_ids, experience_ids | integer[] | with categories / experiences | The chosen experience categories or experiences |
product_category_ids, product_ids | integer[] | with categories / products | The chosen rental categories or rental items |
who_can_buy | string | no | everyone (default): the booking page, POS and the app. staff_only: POS, the app, the API and Dash, never the booking page. off: not for sale; passes already sold keep working |
A pass has to cover something: at least one of the two is not none, and a categories,
experiences or products scope names at least one id. Otherwise the call returns 422. A
category covers everything under it, its subcategories included. all covers every experience
(or rental item) the shop sells, including ones added later. Ids that belong to another shop are
ignored.
Sending valid_for together with valid_from or valid_to returns 422: a pass runs from
purchase or between fixed dates. A monthly pass ignores both.
A monthly pass with no discount_percent returns 422. A shop that cannot take card
payments through Stripe gets 422 memberships_need_stripe when it creates a monthly pass or
switches one to monthly.
Older fieldsโ
These still work, and map onto the fields above:
| Field | Maps to |
|---|---|
covers (items / categories) with the id lists | the matching *_applies_to for each module that has ids, none for the other |
for_sale: false | who_can_buy: off |
sold_online: false | who_can_buy: staff_only |
The response still carries covers, for_sale and sold_online, worked out from the new
fields. Read experience_applies_to, rental_applies_to and who_can_buy instead.
{
"name": "10 visit pass",
"price": 180,
"visits": 10,
"valid_from": "2026-07-01",
"valid_to": "2026-12-31",
"experience_applies_to": "categories",
"experience_category_ids": [3],
"rental_applies_to": "all",
"who_can_buy": "everyone"
}
Returns 201 with the pass.
A pass that runs for 12 months from the day each customer buys it:
{
"name": "10 visit pass",
"price": 115,
"visits": 10,
"valid_for": 12,
"valid_for_unit": "months",
"experience_applies_to": "all"
}
Update a passโ
PATCH /v1/passes/{id}
Scope store:write. Send only the fields you change. Changing a pass never changes the passes
customers already bought: each keeps the name, visits and dates it was sold with, and each
member keeps the monthly price and discount they signed up at.
To switch a pass to running from purchase, send valid_for (its dates are cleared). To switch
it back, send valid_from and/or valid_to on their own, or valid_for: null. Customer passes
already sold keep the dates they were sold with.
Delete a passโ
DELETE /v1/passes/{id}
Scope store:write. Returns 204. A pass that has been sold cannot be deleted and returns
409 pass_sold; set who_can_buy to off instead.