19 operations. Every schema and example on this page is generated from the platform contract.
The merchant schedule view. Occurrences are joined to their class template and carry confirmed + waitlist counts. The default window anchors on the STORE’s calendar day (today → +14 days) because scheduled_date is a store-local date, a naive UTC "today" drifted a day around midnight for any store offset from UTC.
Store that owns the class (also authorises the merchant).
First store-local date to include. Defaults to TODAY in the store’s timezone.
Last store-local date to include. Defaults to 14 days after the start default.
Ordered by date then start time.
Store-local calendar date (YYYY-MM-DD).
Gym wall-clock start, "HH:MM:SS".
Gym wall-clock end, "HH:MM:SS".
'scheduled' | 'cancelled' | …
Derived from start/end time (wrapping past midnight), falling back to the template default.
Per-occurrence override if set, else the template’s instructor.
Derived as max_capacity - spots_remaining.
Level-3 per-occurrence cancellation override; null falls back to Level 2 / Level 1.
The store’s policy for what happens to booked members when a class is moved. Ridden along so a drag-to-move confirm dialog needs no second round-trip. Defaults to reconfirm_warning_only.
notify_onlyreconfirm_warning_onlyreconfirm_or_releasecurl -G "https://www.membber.com/api/v1/classes/occurrences" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
--data-urlencode "store_id=6659c139-0000-4000-8000-d0c500000066"import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.GET("/api/v1/classes/occurrences", {
params: { query: { store_id: "6659c139-0000-4000-8000-d0c500000066" } },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.listClassOccurrences(
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066")
).ok.body.json
print(response){
"occurrences": [
{
"id": "00000d1b-0000-4000-8000-d0c500000000",
"class_id": "2945b542-0000-4000-8000-d0c500000029",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"scheduled_date": "<scheduled_date>",
"start_time": "<start_time>",
"end_time": "<end_time>",
"status": "<status>",
"cancelled_reason": "Added at the front desk",
"created_at": "<created_at>",
"class_name": "<class_name>",
"category": "<category>",
"difficulty": "<difficulty>",
"duration_minutes": 1,
"max_capacity": 1,
"instructor_name": "<instructor_name>",
"coach_id": "c711eee0-0000-4000-8000-d0c5000000c7",
"image_url": "https://example.com/image.jpg",
"require_membership": true,
"allow_drop_in": true,
"drop_in_public_enabled": true,
"drop_in_members_enabled": true,
"drop_in_price_pence": 1500,
"confirmed_count": 1,
"waitlist_count": 1,
"cancellation_cutoff_override_minutes": 1
}
],
"class_move_policy": "notify_only"
}Front-desk bulk attendance: one atomic UPDATE flips every requested booking whose current status allows the transition (attended ← confirmed/no_show, no_show ← confirmed/attended, confirmed ← attended as the Undo). Returns exactly the rows that changed. Naturally idempotent, a retry updates 0 rows and succeeds.
Store that owns the class (also authorises the merchant).
The bookings to flip (max 200). Only rows whose current status allows the transition change.
Target status. 'attended' from confirmed/no_show, 'no_show' from confirmed/attended, 'confirmed' ONLY from attended (the Undo of a bulk mark, it can never resurrect a cancelled booking).
attendedno_showconfirmedThe target status that was applied.
EXACTLY the rows that changed, the only ids a client may flip optimistically.
Booking id that changed.
The booking’s customer.
Requested ids whose current status did not allow the transition.
curl -X POST "https://www.membber.com/api/v1/classes/occurrences/064aae8a-0000-4000-8000-d0c500000006/bulk-attendance" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"booking_ids": [
"ae11dec1-0000-4000-8000-d0c5000000ae"
],
"status": "attended"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/classes/occurrences/{occId}/bulk-attendance", {
params: { path: { occId: "064aae8a-0000-4000-8000-d0c500000006" } },
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
booking_ids: [
"ae11dec1-0000-4000-8000-d0c5000000ae"
],
status: "attended"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.bulkClassAttendance(
path: .init(occId: "064aae8a-0000-4000-8000-d0c500000006"),
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
bookingIds: ["ae11dec1-0000-4000-8000-d0c5000000ae"],
status: .attended
))
).ok.body.json
print(response){
"status": "<status>",
"updated": [
{
"id": "00000d1b-0000-4000-8000-d0c500000000",
"customer_id": "96607d1c-0000-4000-8000-d0c500000096"
}
],
"updated_count": 1,
"skipped_count": 1
}Merchant cancels one class occurrence: atomically cancels all bookings and restores session credits, then runs the counterparty fan-out (push "Class Cancelled" to booked + waitlisted, end promotion Live Activities, Rule C FULL automated drop-in refunds via Stripe, and a live dashboard broadcast). Gated by the can_use_classes entitlement. MONEY-MOVING (issues refunds). Idempotent: a retry on an already-cancelled occurrence returns ALREADY_CANCELLED and the refund engine dedupes per booking so no double-refund is possible.
Store the occurrence belongs to (also authorises the merchant; the occurrence must belong to it).
Optional merchant reason, recorded on the occurrence + audit log + included in the customer push copy. Truncated to 500 chars server-side.
Bookings cancelled by the atomic RPC (booked + waitlisted).
Affected customers whose "Class Cancelled" push was fulfilled.
Drop-in refunds that reached a terminal good state (Rule C full refund).
curl -X POST "https://www.membber.com/api/v1/classes/occurrences/064aae8a-0000-4000-8000-d0c500000006/cancel" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"store_id": "6659c139-0000-4000-8000-d0c500000066"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/classes/occurrences/{occId}/cancel", {
params: { path: { occId: "064aae8a-0000-4000-8000-d0c500000006" } },
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.cancelClassOccurrence(
path: .init(occId: "064aae8a-0000-4000-8000-d0c500000006"),
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066"
))
).ok.body.json
print(response){
"cancelled_bookings": 1,
"members_notified": 1,
"refunds_issued": 1
}Covers "the merchant tweaked the cap or swapped the coach", time and date moves go through /reschedule so they get the full reconfirmation flow. A capacity RAISE does not simply widen spots_remaining (that handed freed seats to whoever booked next and jumped the waitlist queue): it raises the cap and runs cascade_after_booking_release once per freed seat, so each seat becomes a promotion OFFER to the next waitlisted member, with its offer push, or returns to open headroom when nobody is waiting. A capacity LOWER clamps spots_remaining at the confirmed count. Every confirmed booking then gets a "Class details updated" push, and the merchant dashboard is broadcast to. Cancelled or already-finished occurrences are refused. Idempotency `rpc`.
Store that owns the class (also authorises the merchant).
New capacity. May never drop below the current confirmed count (409 CAPACITY_BELOW_BOOKINGS), releasing a member is an explicit, audited action on the remove-member endpoint.
Per-occurrence instructor override. null (or an empty string) clears it back to the template’s.
The updated occurrence: { id, max_capacity, instructor_name, status }.
Confirmed bookings whose "Class details updated" push was fulfilled.
Waitlisted members offered a freed seat by the capacity-raise cascade (each got an offer push).
curl -X PATCH "https://www.membber.com/api/v1/classes/occurrences/064aae8a-0000-4000-8000-d0c500000006/details" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"store_id": "6659c139-0000-4000-8000-d0c500000066"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.PATCH("/api/v1/classes/occurrences/{occId}/details", {
params: { path: { occId: "064aae8a-0000-4000-8000-d0c500000006" } },
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.updateClassOccurrenceDetails(
path: .init(occId: "064aae8a-0000-4000-8000-d0c500000006"),
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066"
))
).ok.body.json
print(response){
"occurrence": {},
"notified_bookings": 1,
"promotions_offered": 1
}Merchant marks one confirmed or attended booking as a no-show: flips the booking status, stamps no_show_marked_at (the timestamp the separate no-show fee/strike cron keys off), clears attended_at, and resyncs the member retention analytics. Gated by the can_use_classes entitlement. Idempotent: marking an already-no-show booking is a no-op success (status already_no_show).
Store the occurrence + booking belong to (also authorises the merchant).
The confirmed or attended booking to mark as a no-show.
'no_show' when this call flipped the booking; 'already_no_show' when it was already marked (idempotent no-op).
no_showalready_no_showWhen this call stamped the no-show (null on the already_no_show no-op, mirroring the legacy replay body).
true when the booking was already a no-show and no write happened.
curl -X POST "https://www.membber.com/api/v1/classes/occurrences/064aae8a-0000-4000-8000-d0c500000006/mark-no-show" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"booking_id": "ae11dec1-0000-4000-8000-d0c5000000ae"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/classes/occurrences/{occId}/mark-no-show", {
params: { path: { occId: "064aae8a-0000-4000-8000-d0c500000006" } },
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
booking_id: "ae11dec1-0000-4000-8000-d0c5000000ae"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.markClassBookingNoShow(
path: .init(occId: "064aae8a-0000-4000-8000-d0c500000006"),
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
bookingId: "ae11dec1-0000-4000-8000-d0c5000000ae"
))
).ok.body.json
print(response){
"status": "no_show",
"no_show_marked_at": "<no_show_marked_at>",
"idempotent": true
}Merchant moves one class occurrence (this-occurrence-only, the parent recurrence is unchanged). Runs the counterparty fan-out: reset reminders, push booked + waitlisted customers, flip re-confirmation flags + enqueue reconfirmation-expiry jobs (material moves), email confirmed bookings via Resend, and update active waitlist Live Activities. Gated by the can_use_classes entitlement. Guards: cannot move a cancelled, past, in-progress, or finished occurrence. NOT money-moving. Idempotent: re-sending the same new values is a no-op (no double notification), matching the legacy route which sends no Idempotency-Key.
Store the occurrence belongs to (also authorises the merchant; the occurrence must belong to it).
New wall-clock date, yyyy-MM-dd. Must not be in the past (store-timezone-aware, checked server-side).
New wall-clock start, HH:mm or HH:mm:ss (store local).
New wall-clock end, HH:mm or HH:mm:ss (store local). Must be after start and within the same day.
The occurrence row after the move (or the unchanged row on a no-op).
true when the requested date/time equalled the current values, so nothing moved and NO customer fan-out ran.
curl -X PATCH "https://www.membber.com/api/v1/classes/occurrences/064aae8a-0000-4000-8000-d0c500000006/reschedule" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"scheduled_date": "<scheduled_date>",
"start_time": "<start_time>",
"end_time": "<end_time>"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.PATCH("/api/v1/classes/occurrences/{occId}/reschedule", {
params: { path: { occId: "064aae8a-0000-4000-8000-d0c500000006" } },
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
scheduled_date: "<scheduled_date>",
start_time: "<start_time>",
end_time: "<end_time>"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.rescheduleClassOccurrence(
path: .init(occId: "064aae8a-0000-4000-8000-d0c500000006"),
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
scheduledDate: "<scheduled_date>",
startTime: "<start_time>",
endTime: "<end_time>"
))
).ok.body.json
print(response){
"occurrence": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"scheduled_date": "<scheduled_date>",
"start_time": "<start_time>",
"end_time": "<end_time>",
"status": "<status>"
},
"no_op": true
}Runs generate_store_occurrences, which creates only the occurrences that do not already exist. Naturally idempotent (idempotency `rpc`): running it twice generates 0 the second time. Normally the cron keeps the schedule topped up; this is the merchant’s manual catch-up.
Store that owns the class (also authorises the merchant).
How far ahead to generate. Defaults to 14; values above 90 are capped at 90.
Occurrences actually created (0 when everything already existed).
The window actually used, after the 90-day cap.
curl -X POST "https://www.membber.com/api/v1/classes/occurrences/generate" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"store_id": "6659c139-0000-4000-8000-d0c500000066"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/classes/occurrences/generate", {
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.generateClassOccurrences(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066"
))
).ok.body.json
print(response){
"generated": 1,
"days_ahead": 1
}The Schedule Builder’s "tap an empty cell" flow. The occurrence is tagged source="manual" so a later recurrence regeneration can NEVER delete it, and spots_remaining is set explicitly (the column default is 0, which would otherwise create a class that is instantly full). Rejects past dates in the store’s timezone and any class that would run past midnight. In inline-create mode every rejection after the template insert rolls that template back, so a failed quick-add leaves no orphan. ⚠️ NOT idempotent: this is a plain INSERT and `class_occurrences` has no unique constraint beyond its primary key, so a retried quick-add places a SECOND class at the same time. Do not blind-retry a timeout, re-read the schedule and confirm the occurrence is absent first. (Same behaviour as the legacy route this twins; a dedup key is deliberately not claimed here rather than falsely asserted.)
Store that owns the class (also authorises the merchant).
TEMPLATE MODE: an existing ACTIVE class template to schedule. When omitted, `name` switches to INLINE-CREATE MODE.
INLINE-CREATE MODE: an active template with the same normalised name is silently reused if one exists, otherwise a fresh template is minted alongside the occurrence.
Store-local date. Must not be before TODAY in the store’s timezone.
Gym wall-clock start, "HH:MM" or "HH:MM:SS".
Per-occurrence override, 15-240. Falls back to the template, then 60.
Per-occurrence override, 1-100. Falls back to the template, then 15.
Inline-create mode only: category for the new template.
Overrides the template coach; the coach’s name is copied onto the occurrence.
The created occurrence, with the template’s metadata flattened in so the client needs no refetch.
Store-local calendar date (YYYY-MM-DD).
Gym wall-clock start, "HH:MM:SS".
Gym wall-clock end, "HH:MM:SS".
'scheduled' | 'cancelled' | …
Derived from start/end time (wrapping past midnight), falling back to the template default.
Per-occurrence override if set, else the template’s instructor.
Derived as max_capacity - spots_remaining.
Level-3 per-occurrence cancellation override; null falls back to Level 2 / Level 1.
curl -X POST "https://www.membber.com/api/v1/classes/occurrences/quick-add" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"scheduled_date": "<scheduled_date>",
"start_time": "<start_time>"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/classes/occurrences/quick-add", {
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
scheduled_date: "<scheduled_date>",
start_time: "<start_time>"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.quickAddClassOccurrence(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
scheduledDate: "<scheduled_date>",
startTime: "<start_time>"
))
).ok.body.json
print(response){
"occurrence": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"class_id": "2945b542-0000-4000-8000-d0c500000029",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"scheduled_date": "<scheduled_date>",
"start_time": "<start_time>",
"end_time": "<end_time>",
"status": "<status>",
"cancelled_reason": "Added at the front desk",
"created_at": "<created_at>",
"class_name": "<class_name>",
"category": "<category>",
"difficulty": "<difficulty>",
"duration_minutes": 1,
"max_capacity": 1,
"instructor_name": "<instructor_name>",
"coach_id": "c711eee0-0000-4000-8000-d0c5000000c7",
"image_url": "https://example.com/image.jpg",
"require_membership": true,
"allow_drop_in": true,
"drop_in_public_enabled": true,
"drop_in_members_enabled": true,
"drop_in_price_pence": 1500,
"confirmed_count": 1,
"waitlist_count": 1,
"cancellation_cutoff_override_minutes": 1
}
}Date filters run in Postgres; text, category and instructor filters run over the fetched page because PostgREST cannot filter on a joined table, so `total` is the match count within that page, not a global count. Only status="scheduled" occurrences are returned.
Store that owns the class (also authorises the merchant).
Free text matched against class name, description and instructor.
Exact (case-insensitive) category match.
Substring match on the occurrence or template instructor.
Earliest store-local date (YYYY-MM-DD).
Latest store-local date (YYYY-MM-DD).
Page size, default 30.
Page offset, default 0.
Matching occurrences with their joined class template.
Number of matches WITHIN the fetched page. The text/category/instructor filters run after paging (PostgREST cannot filter on a joined table), so this is not a database-wide count.
curl -G "https://www.membber.com/api/v1/classes/occurrences/search" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
--data-urlencode "store_id=6659c139-0000-4000-8000-d0c500000066"import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.GET("/api/v1/classes/occurrences/search", {
params: { query: { store_id: "6659c139-0000-4000-8000-d0c500000066" } },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.searchClassOccurrences(
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066")
).ok.body.json
print(response){
"occurrences": [
{}
],
"total": 1
}The merchant class library. Returns every `gym_classes` row for the store, including inactive ones, so the editor can show archived types, each enriched with its published class media and its recent + future skip dates, plus the store’s active coaches. Gated by the can_use_classes entitlement.
Store that owns the class (also authorises the merchant).
Every class template for the store INCLUDING inactive ones, ordered by name.
Active coaches for the store, ordered by name.
curl -G "https://www.membber.com/api/v1/classes/templates" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
--data-urlencode "store_id=6659c139-0000-4000-8000-d0c500000066"import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.GET("/api/v1/classes/templates", {
params: { query: { store_id: "6659c139-0000-4000-8000-d0c500000066" } },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.listClassTemplates(
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066")
).ok.body.json
print(response){
"classes": [
{}
],
"coaches": [
{}
]
}Creates a `gym_classes` row. When a legacy single-time recurrence (recurring_days + recurring_time) is supplied it first probes venue capacity across the next 14 days and REJECTS with VENUE_CAPACITY_EXCEEDED (409) if the class would overfill the venue, deleting the just-created row so no orphan survives, then persists any skip dates and generates 28 days of occurrences. A duplicate active name answers TEMPLATE_NAME_TAKEN (409). Idempotency is `rpc`: the partial unique index on (store, lower(trim(name))) makes a retried create collide rather than duplicate.
Store that owns the class (also authorises the merchant).
Display name. Must be unique per store among ACTIVE classes.
Long description shown to members.
Free-text grouping (e.g. "Strength").
Free-text level (e.g. "Beginner").
Display instructor. IGNORED when `coach_id` is sent and resolves, the coach name wins.
Link to a `gym_coaches` row; its name is copied into instructor_name. null clears the link.
Default class length, 15-240 minutes.
Default capacity, 1-100 people.
Must be a URL produced by the class image uploader (cropped for mobile); anything else is rejected.
Optional class video.
Member-facing kit list.
Only members may book.
Master switch for paid drop-ins.
Non-members may buy a drop-in.
Members may buy a drop-in.
Drop-in price in pence (0-100000). null clears it.
Legacy single-time recurrence: day-of-week strings '0'(Sun)..'6'(Sat).
Legacy single-time recurrence start time, "HH:MM:SS".
Last date the recurrence generates. Must not be in the past.
Dates the recurrence should skip. Rare on create, usually authored after the template exists.
Store-local calendar date to skip (YYYY-MM-DD).
Optional merchant note, max 200 chars (e.g. "Bank holiday").
The created `gym_classes` row.
Occurrences generated for the next 28 days (0 unless a legacy single-time recurrence was supplied).
curl -X POST "https://www.membber.com/api/v1/classes/templates" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"store_id": "6659c139-0000-4000-8000-d0c500000066"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/classes/templates", {
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.createClassTemplate(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066"
))
).ok.body.json
print(response){
"class": {},
"occurrences_generated": 1
}🔴 MONEY-MOVING. Sets the class inactive and then, for EVERY future scheduled occurrence (>= today in the store’s timezone): runs cancel_class_occurrence_atomic (cancels all bookings and restores members’ class credits in one transaction, a raw update would burn those credits forever), then runs the shared runCancelOccurrenceConsequences fan-out, "Class Cancelled" push to booked AND waitlisted members, promotion Live Activity ends, Rule C FULL drop-in refunds via Stripe, waitlist dashboard broadcast, and per-occurrence audit rows. The fan-out is AWAITED before responding, so a 200 means every notification and refund was attempted. Idempotent: a repeat delete finds the occurrences already cancelled (benign) and the refund engine dedupes per (booking_id, trigger_reason), so it can never double-refund.
Store that owns the class (also authorises the merchant).
Future scheduled occurrences cancelled by the atomic RPC.
Affected customers whose "Class Cancelled" push was fulfilled across all cancelled occurrences.
Drop-in refunds that reached a terminal good state (Rule C FULL refund).
Occurrences whose cancel or fan-out had a failure. Non-zero still returns success, the class IS deactivated and the failures are captured to Sentry for the mop-up crons.
curl -X DELETE "https://www.membber.com/api/v1/classes/templates/32e134b3-0000-4000-8000-d0c500000032?store_id=6659c139-0000-4000-8000-d0c500000066" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678"import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.DELETE("/api/v1/classes/templates/{classId}", {
params: { path: { classId: "32e134b3-0000-4000-8000-d0c500000032" }, query: { store_id: "6659c139-0000-4000-8000-d0c500000066" } },
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.deleteClassTemplate(
path: .init(classId: "32e134b3-0000-4000-8000-d0c500000032"),
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066")
).ok.body.json
print(response){
"occurrences_cancelled": 1,
"members_notified": 1,
"refunds_issued": 1,
"failed_occurrences": 1
}Partial update of a `gym_classes` row. When (and ONLY when) recurrence actually changed AND the class is recurring on either side of the edit, the skip-date replace + delete-unbooked + regenerate run inside ONE Postgres transaction (apply_class_recurrence_change), so a failure mid-flight cannot leave a half-edited schedule. Occurrences are only ever deleted from TOMORROW in the store’s timezone onward, today’s classes are never removed, and booked occurrences always survive. A class that is one-off both before and after the edit never enters the regen path. Idempotency `rpc`: re-sending the same patch converges on the same row.
Store that owns the class (also authorises the merchant).
Display name. Must be unique per store among ACTIVE classes.
Long description shown to members.
Free-text grouping (e.g. "Strength").
Free-text level (e.g. "Beginner").
Display instructor. IGNORED when `coach_id` is sent and resolves, the coach name wins.
Link to a `gym_coaches` row; its name is copied into instructor_name. null clears the link.
Default class length, 15-240 minutes.
Default capacity, 1-100 people.
Must be a URL produced by the class image uploader (cropped for mobile); anything else is rejected.
Optional class video.
Member-facing kit list.
Only members may book.
Master switch for paid drop-ins.
Non-members may buy a drop-in.
Members may buy a drop-in.
Drop-in price in pence (0-100000). null clears it.
Legacy single-time recurrence: day-of-week strings '0'(Sun)..'6'(Sat).
Legacy single-time recurrence start time, "HH:MM:SS".
Last date the recurrence generates. Must not be in the past.
Archive / restore the template without deleting it.
REPLACES the whole skip-date set when sent. Sending it also counts as a recurrence change, which triggers the atomic delete-unbooked + regenerate for a recurring class.
Store-local calendar date to skip (YYYY-MM-DD).
Optional merchant note, max 200 chars (e.g. "Bank holiday").
Optimistic concurrency: the `updated_at` you last read. If it no longer matches, the write is refused with TEMPLATE_STALE (409) and the current value is returned, so device A cannot silently overwrite device B.
The updated `gym_classes` row.
Present ONLY when the atomic recurrence regen ran: { success, skip_dates_persisted, deleted_count, generated_count }. null when the edit did not touch recurrence.
curl -X PATCH "https://www.membber.com/api/v1/classes/templates/32e134b3-0000-4000-8000-d0c500000032" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"store_id": "6659c139-0000-4000-8000-d0c500000066"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.PATCH("/api/v1/classes/templates/{classId}", {
params: { path: { classId: "32e134b3-0000-4000-8000-d0c500000032" } },
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.updateClassTemplate(
path: .init(classId: "32e134b3-0000-4000-8000-d0c500000032"),
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066"
))
).ok.body.json
print(response){
"class": {},
"recurrence_apply": "<recurrence_apply>"
}Powers the merchant review screen BEFORE they save a schedule change. PURE READ, writes nothing, which is why it declares mutates=false despite being POST-shaped (the proposed schedule is too structured for a query string). The create-count comes from the count_planned_occurrences RPCs, which mirror the generator exactly (including its venue-capacity skip logic), so the previewed number equals what will actually generate. If a count RPC fails the count falls back to 0 rather than failing the preview.
Store that owns the class (also authorises the merchant).
Proposed day-of-week strings '0'(Sun)..'6'(Sat). Falls back to the class’s current value.
Proposed start time "HH:MM[:SS]". Falls back to current.
Proposed last date. Explicit null means "no end date".
Proposed skip dates.
YYYY-MM-DD.
Proposed MULTI-SLOT set. When present it is counted instead of the single-time fields.
Day-of-week strings this slot runs on: '0'(Sun)..'6'(Sat).
Gym wall-clock start, "HH:MM" or "HH:MM:SS" (normalised server-side).
Per-slot length override; falls back to the class template’s duration.
Per-slot capacity override; falls back to the class template’s capacity.
Per-slot instructor override; falls back to the class template’s.
Future UNBOOKED occurrences that would be deleted (>= tomorrow in the store’s timezone).
Future BOOKED occurrences that would survive on the OLD schedule, with head counts so the merchant knows exactly who they would need to message.
Occurrences the proposed schedule would generate over the next 28 days.
curl -X POST "https://www.membber.com/api/v1/classes/templates/32e134b3-0000-4000-8000-d0c500000032/recurrence-preview" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"store_id": "6659c139-0000-4000-8000-d0c500000066"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/classes/templates/{classId}/recurrence-preview", {
params: { path: { classId: "32e134b3-0000-4000-8000-d0c500000032" } },
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.previewClassRecurrenceChange(
path: .init(classId: "32e134b3-0000-4000-8000-d0c500000032"),
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066"
))
).ok.body.json
print(response){
"to_remove_count": 1,
"booked_to_preserve": [
{
"occurrence_id": "8770ea49-0000-4000-8000-d0c500000087",
"scheduled_date": "<scheduled_date>",
"start_time": "<start_time>",
"confirmed_count": 1,
"waitlist_count": 1
}
],
"to_create_count": 1
}The multi-slot Schedule Builder’s read. Returns only ACTIVE slots (removed slots are soft-deactivated and stay in the table for history) plus the parent template’s duration / capacity / instructor, which any slot field left null inherits.
Store that owns the class (also authorises the merchant).
ACTIVE slots only, ordered by start time.
The parent class template’s values, which any slot field left null inherits.
curl -G "https://www.membber.com/api/v1/classes/templates/32e134b3-0000-4000-8000-d0c500000032/slots" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
--data-urlencode "store_id=6659c139-0000-4000-8000-d0c500000066"import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.GET("/api/v1/classes/templates/{classId}/slots", {
params: { path: { classId: "32e134b3-0000-4000-8000-d0c500000032" }, query: { store_id: "6659c139-0000-4000-8000-d0c500000066" } },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.listClassRecurringSlots(
path: .init(classId: "32e134b3-0000-4000-8000-d0c500000032"),
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066")
).ok.body.json
print(response){
"slots": [
{}
],
"class_defaults": {
"duration_minutes": 1,
"max_capacity": 1,
"instructor_name": "<instructor_name>"
}
}Adds a single slot and regenerates WITHOUT deleting anything, nothing was removed, so nothing is cleaned up. A duplicate start time for the same class is refused (DUPLICATE_SLOT_TIME, 409) by the partial unique index, which is also what makes a retry safe (idempotency `rpc`). If the regeneration itself fails the slot is still saved and the next cron cycle catches the occurrences up.
Day-of-week strings this slot runs on: '0'(Sun)..'6'(Sat).
Gym wall-clock start, "HH:MM" or "HH:MM:SS" (normalised server-side).
Per-slot length override; falls back to the class template’s duration.
Per-slot capacity override; falls back to the class template’s capacity.
Per-slot instructor override; falls back to the class template’s.
Store that owns the class (also authorises the merchant).
The inserted slot.
What the regeneration created.
curl -X POST "https://www.membber.com/api/v1/classes/templates/32e134b3-0000-4000-8000-d0c500000032/slots" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"days": [
"<day>"
],
"start_time": "<start_time>",
"store_id": "6659c139-0000-4000-8000-d0c500000066"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/classes/templates/{classId}/slots", {
params: { path: { classId: "32e134b3-0000-4000-8000-d0c500000032" } },
body: {
days: [
"<day>"
],
start_time: "<start_time>",
store_id: "6659c139-0000-4000-8000-d0c500000066"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.addClassRecurringSlot(
path: .init(classId: "32e134b3-0000-4000-8000-d0c500000032"),
body: .json(.init(
days: ["<day>"],
startTime: "<start_time>",
storeId: "6659c139-0000-4000-8000-d0c500000066"
))
).ok.body.json
print(response){
"slot": {},
"recurrence_apply": "<recurrence_apply>"
}The canonical editor save. Deactivating the current slots, inserting the new set, deleting unbooked future occurrences and regenerating all happen inside ONE Postgres transaction (replace_class_recurring_slots), so a failure mid-flight cannot leave a half-saved schedule. Only occurrences from TOMORROW in the store’s timezone onward, with source="recurring", are ever deleted, hand-placed classes and any BOOKED occurrence survive on their original time. Idempotency `rpc`.
Store that owns the class (also authorises the merchant).
The COMPLETE new slot set. An empty array removes all recurrence.
Day-of-week strings this slot runs on: '0'(Sun)..'6'(Sat).
Gym wall-clock start, "HH:MM" or "HH:MM:SS" (normalised server-side).
Per-slot length override; falls back to the class template’s duration.
Per-slot capacity override; falls back to the class template’s capacity.
Per-slot instructor override; falls back to the class template’s.
Optimistic concurrency against the class template’s `updated_at`; mismatch → TEMPLATE_STALE (409).
The resolved active slots after the save (server truth).
The parent class template’s values, which any slot field left null inherits.
What the transaction did: deleted_count / generated_count for the regenerated window.
curl -X PUT "https://www.membber.com/api/v1/classes/templates/32e134b3-0000-4000-8000-d0c500000032/slots" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"slots": [
{
"days": [
"<day>"
],
"start_time": "<start_time>"
}
]
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.PUT("/api/v1/classes/templates/{classId}/slots", {
params: { path: { classId: "32e134b3-0000-4000-8000-d0c500000032" } },
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
slots: [
{
days: [
"<day>"
],
start_time: "<start_time>"
}
]
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.replaceClassRecurringSlots(
path: .init(classId: "32e134b3-0000-4000-8000-d0c500000032"),
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
slots: [.init(
days: ["<day>"],
startTime: "<start_time>"
)]
))
).ok.body.json
print(response){
"slots": [
{}
],
"class_defaults": {
"duration_minutes": 1,
"max_capacity": 1,
"instructor_name": "<instructor_name>"
},
"recurrence_apply": "<recurrence_apply>"
}Soft-deactivates the slot (never a hard delete, so audit history survives), then deletes unbooked future occurrences at that time and regenerates. BOOKED occurrences are deliberately left standing on the removed time, member contracts are honoured, and the response reports exactly how many member bookings that is, so the merchant knows who still needs handling. Idempotency `rpc`: removing an already-removed slot is benign.
Store that owns the class (also authorises the merchant).
What removing the slot did to the schedule.
Unbooked future occurrences removed at that time.
Member bookings (confirmed + waitlisted + promotion_pending) that STILL STAND on the removed time, the merchant must honour or message these.
curl -X DELETE "https://www.membber.com/api/v1/classes/templates/32e134b3-0000-4000-8000-d0c500000032/slots/ca636bb9-0000-4000-8000-d0c5000000ca?store_id=6659c139-0000-4000-8000-d0c500000066" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678"import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.DELETE("/api/v1/classes/templates/{classId}/slots/{slotId}", {
params: { path: { classId: "32e134b3-0000-4000-8000-d0c500000032", slotId: "ca636bb9-0000-4000-8000-d0c5000000ca" }, query: { store_id: "6659c139-0000-4000-8000-d0c500000066" } },
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.removeClassRecurringSlot(
path: .init(classId: "32e134b3-0000-4000-8000-d0c500000032", slotId: "ca636bb9-0000-4000-8000-d0c5000000ca"),
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066")
).ok.body.json
print(response){
"recurrence_apply": {
"deleted_count": 1,
"preserved_booked_count": 1
}
}Merges the sent fields over the stored ones and re-validates the FINAL state, so cross-field rules such as "must not run past midnight" are checked against what the slot will actually become, then writes only the fields you sent. An empty patch is a no-op that returns the current row. After a real write it deletes unbooked future occurrences and regenerates, so the new time takes effect from tomorrow onward while BOOKED occurrences stay on the old time. Idempotency `rpc`.
Store that owns the class (also authorises the merchant).
Day-of-week strings '0'(Sun)..'6'(Sat).
Gym wall-clock start, "HH:MM" or "HH:MM:SS".
The slot after the update (or unchanged, when the patch was empty).
What the regeneration deleted / created.
curl -X PATCH "https://www.membber.com/api/v1/classes/templates/32e134b3-0000-4000-8000-d0c500000032/slots/ca636bb9-0000-4000-8000-d0c5000000ca" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"store_id": "6659c139-0000-4000-8000-d0c500000066"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.PATCH("/api/v1/classes/templates/{classId}/slots/{slotId}", {
params: { path: { classId: "32e134b3-0000-4000-8000-d0c500000032", slotId: "ca636bb9-0000-4000-8000-d0c5000000ca" } },
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.updateClassRecurringSlot(
path: .init(classId: "32e134b3-0000-4000-8000-d0c500000032", slotId: "ca636bb9-0000-4000-8000-d0c5000000ca"),
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066"
))
).ok.body.json
print(response){
"slot": {},
"recurrence_apply": "<recurrence_apply>"
}