34 operations. Every schema and example on this page is generated from the platform contract.
Books a walk-up / phone appointment as the merchant, into a unit the authenticated store owns. Wraps the one booking authority, so a normal add still cannot double-book. With `override: true` the merchant FORCE-BOOKS a squeeze-in the authority would refuse, the resulting block is exempt from the double-booking guard and blocks nobody. Idempotent per `client_token`. No money moves.
The staff member / room / table to book. Must belong to the authenticated store.
ISO instant the appointment starts.
ISO instant the appointment ends.
An existing customer to book for.
Who the appointment is for, for a walk-up with no account.
Required if there is no customer_id, someone must be reachable.
Agreed price, snapshotted onto the appointment.
Widen what the UNIT is occupied for (buffers), without changing what the customer is told.
FORCE-BOOK: squeeze this in even where the availability authority would refuse. The merchant-only power, a squeeze-in blocks nobody else. Defaults to false.
Caller-generated idempotency key, a retry with the same token returns the SAME appointment, never a duplicate.
How many of the party are children (age 12 and under). 0 for a normal appointment.
High chairs the diner asked for, so the host can fetch them.
true when an idempotent retry returned the appointment the first call created.
curl -X POST "https://www.membber.com/api/v1/bookings/appointments" \
-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",
"unit_id": "eeebf9b6-0000-4000-8000-d0c5000000ee",
"starts_at": "<starts_at>",
"ends_at": "<ends_at>"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.createMerchantAppointment({
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
unit_id: "eeebf9b6-0000-4000-8000-d0c5000000ee",
starts_at: "<starts_at>",
ends_at: "<ends_at>"
},
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.createMerchantAppointment(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
unitId: "eeebf9b6-0000-4000-8000-d0c5000000ee",
startsAt: "<starts_at>",
endsAt: "<ends_at>"
))
).ok.body.json
print(response){
"appointment": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"unit_id": "eeebf9b6-0000-4000-8000-d0c5000000ee",
"service_id": "993232e5-0000-4000-8000-d0c500000099",
"customer_id": "96607d1c-0000-4000-8000-d0c500000096",
"guest_name": "<guest_name>",
"guest_email": "alex@example.com",
"guest_phone": "+44 7700 900123",
"starts_at": "<starts_at>",
"ends_at": "<ends_at>",
"status": "<status>",
"payment_status": "<payment_status>",
"price_pence": 1500,
"deposit_pence": 1500,
"balance_pence": 1500,
"currency": "GBP",
"party_size": 1,
"children": 1,
"high_chairs": 1,
"source": "<source>",
"is_override": true
},
"reused": true
}Cancels a booking the authenticated store owns and frees its slot (the block is RELEASED, never deleted, so the audit trail survives). Idempotent, cancelling twice is a no-op, never a second state change. No fee is charged here (cancel/no-show fees are Stage 7).
The appointment to cancel. Must belong to the authenticated store.
Why, for the record.
true when it was already cancelled/completed, an idempotent no-op.
curl -X POST "https://www.membber.com/api/v1/bookings/appointments/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",
"appointment_id": "0d727dbb-0000-4000-8000-d0c50000000d"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.cancelMerchantAppointment({
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
appointment_id: "0d727dbb-0000-4000-8000-d0c50000000d"
},
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.cancelMerchantAppointment(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
appointmentId: "0d727dbb-0000-4000-8000-d0c50000000d"
))
).ok.body.json
print(response){
"appointment_id": "0d727dbb-0000-4000-8000-d0c50000000d",
"cancelled": true,
"noop": true
}Marks a confirmed appointment as checked-in (they have arrived). Single-winner: only a confirmed booking can be checked in, and a second check-in is a no-op. Store-scoped. Audited. No money moves.
The appointment. Must belong to the authenticated store.
The status the appointment now holds.
true when it was already in that status, an idempotent no-op.
curl -X POST "https://www.membber.com/api/v1/bookings/appointments/check-in" \
-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",
"appointment_id": "0d727dbb-0000-4000-8000-d0c50000000d"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.checkInAppointment({
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
appointment_id: "0d727dbb-0000-4000-8000-d0c50000000d"
},
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.checkInAppointment(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
appointmentId: "0d727dbb-0000-4000-8000-d0c50000000d"
))
).ok.body.json
print(response){
"appointment_id": "0d727dbb-0000-4000-8000-d0c50000000d",
"status": "<status>",
"noop": true
}Marks an appointment done, from confirmed or checked-in. Single-winner: a second complete is a no-op, and a cancelled / no-show booking cannot be completed. Store-scoped. Audited. No money moves (settle is Stage 6).
The appointment. Must belong to the authenticated store.
The status the appointment now holds.
true when it was already in that status, an idempotent no-op.
curl -X POST "https://www.membber.com/api/v1/bookings/appointments/complete" \
-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",
"appointment_id": "0d727dbb-0000-4000-8000-d0c50000000d"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.completeAppointment({
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
appointment_id: "0d727dbb-0000-4000-8000-d0c50000000d"
},
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.completeAppointment(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
appointmentId: "0d727dbb-0000-4000-8000-d0c50000000d"
))
).ok.body.json
print(response){
"appointment_id": "0d727dbb-0000-4000-8000-d0c50000000d",
"status": "<status>",
"noop": true
}Marks a DUE confirmed appointment as a no-show (the time has passed and nobody came). A checked-in guest showed up, so a no-show cannot fire from checked-in; a future booking is not yet due and is refused. Single-winner and idempotent. Store-scoped. Audited. NO fee is charged here (no-show fees are Stage 7).
The appointment. Must belong to the authenticated store.
The status the appointment now holds.
true when it was already in that status, an idempotent no-op.
curl -X POST "https://www.membber.com/api/v1/bookings/appointments/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",
"appointment_id": "0d727dbb-0000-4000-8000-d0c50000000d"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.markAppointmentNoShow({
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
appointment_id: "0d727dbb-0000-4000-8000-d0c50000000d"
},
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.markAppointmentNoShow(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
appointmentId: "0d727dbb-0000-4000-8000-d0c50000000d"
))
).ok.body.json
print(response){
"appointment_id": "0d727dbb-0000-4000-8000-d0c50000000d",
"status": "<status>",
"noop": true
}Secures the NEW slot before releasing the OLD, so a half-failed move leaves the original untouched. Both the appointment and the destination unit must belong to the authenticated store. With `override: true` the merchant moves past the cancellation window and past the free-slot check (their diary, their call), a power the customer reschedule never has.
The appointment to move. Must belong to the authenticated store.
Where it is moving to, may be the same unit. Must belong to the authenticated store.
ISO instant of the new start.
ISO instant of the new end.
Move even inside the cancellation policy window and even onto an occupied slot (a merchant squeeze-move). The merchant-only power. Defaults to false (respects the window and the free-slot check).
true when the booking was nudged within the same unit.
curl -X POST "https://www.membber.com/api/v1/bookings/appointments/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",
"appointment_id": "0d727dbb-0000-4000-8000-d0c50000000d",
"unit_id": "eeebf9b6-0000-4000-8000-d0c5000000ee",
"starts_at": "<starts_at>",
"ends_at": "<ends_at>"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.rescheduleMerchantAppointment({
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
appointment_id: "0d727dbb-0000-4000-8000-d0c50000000d",
unit_id: "eeebf9b6-0000-4000-8000-d0c5000000ee",
starts_at: "<starts_at>",
ends_at: "<ends_at>"
},
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.rescheduleMerchantAppointment(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
appointmentId: "0d727dbb-0000-4000-8000-d0c50000000d",
unitId: "eeebf9b6-0000-4000-8000-d0c5000000ee",
startsAt: "<starts_at>",
endsAt: "<ends_at>"
))
).ok.body.json
print(response){
"appointment_id": "0d727dbb-0000-4000-8000-d0c50000000d",
"unit_id": "eeebf9b6-0000-4000-8000-d0c5000000ee",
"starts_at": "<starts_at>",
"ends_at": "<ends_at>",
"moved_in_place": true
}Answers "when was this person last in?" across the store's WHOLE history, grouped by person rather than by row. Store-wide and business-authenticated; the store is the authenticated store, never a client-supplied id. A customer who has been deleted under the data-integrity law is unfindable here: their financial rows stay in the books, but their identity cannot be searched back out of them. Counts are real statuses only, visits means completed or checked-in, and no-shows, cancellations and unresolved past bookings are each reported as themselves.
Part of a name or an email address. Two characters minimum: one letter matches most of a roster.
How many people to return. Default 20.
How many recent appointments per person. Default 12.
The store's IANA timezone, every instant above is anchored to it for display.
The normalised needle actually searched for.
How many people matched in total, counted BEFORE `limit`, so a client can say "20 of 34" honestly.
Most recently seen first.
Stable grouping key for this person within this store. `c:<customer_id>` for an account booking, `g:<name>|<email>` for a guest, a guest has no id, so name plus email is the most honest identity the data supports. Two different people sharing both would merge; nothing in the data can separate them.
Set only where the booking was made from an account.
The most recent name this person booked under.
Times they were actually in the chair: completed + checked-in. A no-show is not a visit and is counted separately.
Past appointments still sitting in `confirmed`, nobody ever said whether they came. Reported on its own rather than guessed either way; this is the state B497 found two of yesterday's rows in.
ISO instant of their first actual visit.
ISO instant of their most recent actual visit, the answer to the question.
ISO instant of their soonest future booking, if any.
Sum of the price on visits that happened. Not a forecast, not a lifetime value.
Their most recent appointments, newest first, capped by `visit_limit`.
ISO instant the appointment started.
'completed' | 'checked_in' | 'confirmed' | 'cancelled' | 'no_show' | 'pending_payment'.
Who or what they were booked with.
curl -G "https://www.membber.com/api/v1/bookings/appointments/search" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
--data-urlencode "store_id=6659c139-0000-4000-8000-d0c500000066" \
--data-urlencode "q=<q>"import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.GET("/api/v1/bookings/appointments/search", {
params: { query: { store_id: "6659c139-0000-4000-8000-d0c500000066", q: "<q>" } },
});
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.searchAppointmentPeople(
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066", q: "<q>")
).ok.body.json
print(response){
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"timezone": "Europe/London",
"query": "<query>",
"total": 1,
"people": [
{
"key": "<key>",
"customer_id": "96607d1c-0000-4000-8000-d0c500000096",
"name": "Example name",
"email": "alex@example.com",
"currency": "GBP",
"visits": 1,
"no_shows": 1,
"cancelled": 1,
"unresolved": 1,
"first_visit": "<first_visit>",
"last_visit": "<last_visit>",
"next_visit": "<next_visit>",
"spend_pence": 1500,
"recent": [
{
"id": "00000d1b-0000-4000-8000-d0c500000000",
"starts_at": "<starts_at>",
"ends_at": "<ends_at>",
"status": "<status>",
"payment_status": "<payment_status>",
"price_pence": 1500,
"currency": "GBP",
"unit_name": "<unit_name>"
}
]
}
]
}The merchant takes the outstanding balance at the end of an appointment, in cash or on their own card machine, and records it here. NO Stripe money moves and no platform fee is taken, the money changed hands outside Membber, so this row is the only evidence it was collected. The amount is DERIVED server-side (price − deposit actually paid − everything already settled) and is never accepted from the client. Settling completes the appointment. A short settle leaves the residual collectable: the no-op gate is "nothing outstanding", never "already stamped", and the recorded total accumulates. Nothing outstanding is an idempotent no-op rather than an error, and a retry can never record twice. Losing a race to a colleague comes back as outcome=already_settled with who/how/how much, never as your own success. A no-show is never settled here (that is the no-show fee path). Store-scoped and business-authed; gated by the can_use_bookings entitlement + the can_process_payments staff permission.
Store the appointment belongs to (authorisation only, the store acted on always comes from auth).
The appointment to settle. Must belong to the authenticated store.
How the money was taken. Both are RECORDED ONLY (the money moved outside Membber, at the counter or on the shop's own terminal, so this is the only record it was collected). No Stripe charge is made and no platform fee is taken.
cashcard_machine'settled' = this call recorded the money. 'already_settled' = somebody else's settle got there first and this call recorded nothing (never draw it as your own success, the person who lost may be holding the cash). 'nothing_owed' = there was nothing left to take.
settledalready_settlednothing_owedThe appointment status after the call, settling completes the appointment.
'none' | 'deposit_paid' | 'paid' | 'refunded' | 'partially_refunded' | 'failed'.
What is STILL owed AFTER this call, derived server-side as price − deposit actually paid − everything settled. Unambiguous on every outcome, so a client can write it straight onto the row it is showing.
What THIS call recorded. 0 on any outcome other than 'settled'.
Everything ever recorded as settled on this booking, this call included.
How the settle being reported was taken (null when nothing was ever owed).
cashcard_machineISO instant of the settle being reported.
true when the settle being reported is the caller's own.
Who recorded it, when that was somebody else and we can name them. null = we cannot say who.
curl -X POST "https://www.membber.com/api/v1/bookings/appointments/settle" \
-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",
"appointment_id": "0d727dbb-0000-4000-8000-d0c50000000d",
"method": "cash"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/bookings/appointments/settle", {
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
appointment_id: "0d727dbb-0000-4000-8000-d0c50000000d",
method: "cash"
},
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.settleAppointmentBalance(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
appointmentId: "0d727dbb-0000-4000-8000-d0c50000000d",
method: .cash
))
).ok.body.json
print(response){
"outcome": "settled",
"appointment_id": "0d727dbb-0000-4000-8000-d0c50000000d",
"status": "<status>",
"payment_status": "<payment_status>",
"outstanding_pence": 1500,
"collected_pence": 1500,
"settled_total_pence": 1500,
"currency": "GBP",
"method": "cash",
"settled_at": "<settled_at>",
"settled_by_you": true,
"settled_by_name": "<settled_by_name>"
}The merchant view of the diary: every active bookable unit for a date RANGE (store-local dates), each carrying its appointments, live holds and time-off. Store-wide and business-authenticated, the customer appointments read is caller-scoped and cannot see another booking. Timezone-correct: day boundaries read the store timezone.
First day to show, in the STORE's timezone.
Last day to show (inclusive), in the STORE's timezone.
Restrict to one unit. Omit for every unit (the full diary).
Include cancelled appointments. Defaults to excluding them.
The store's IANA timezone, every instant below is anchored to it for display.
ISO instant the window starts (00:00 store-local on from_date).
ISO instant the window ends, EXCLUSIVE (00:00 store-local the day after to_date).
Every active bookable unit, each with its appointments, holds and time-off.
Diary column colour.
ISO instant the appointment starts.
ISO instant the appointment ends (what the customer was told).
'pending_payment' | 'confirmed' | 'checked_in' | 'completed' | 'cancelled' | 'no_show'.
How many of the party are children (age 12 and under). 0 for a normal appointment.
High chairs the diner asked for, so the host can fetch them.
The dining AREA the guest asked for at booking (Level-1 seating). Null for no-preference and every non-restaurant booking.
Resolved name of preferred_area_id (e.g. "Window"), joined from restaurant_areas. Null when there is no preference.
true when this was FORCE-BOOKED (a squeeze-in exempt from the double-booking guard).
Live pending reservations mid-checkout.
When this pending reservation lapses.
Time-off / lunch, from both the time-off entity and manual blocks.
'time_off' (an explicit time-off entry) or 'block' (a manual lunch/errand block).
curl -G "https://www.membber.com/api/v1/bookings/diary" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
--data-urlencode "store_id=6659c139-0000-4000-8000-d0c500000066" \
--data-urlencode "from_date=<from_date>" \
--data-urlencode "to_date=<to_date>"import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.GET("/api/v1/bookings/diary", {
params: { query: { store_id: "6659c139-0000-4000-8000-d0c500000066", from_date: "<from_date>", to_date: "<to_date>" } },
});
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.getStoreDiary(
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066", fromDate: "<from_date>", toDate: "<to_date>")
).ok.body.json
print(response){
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"timezone": "Europe/London",
"from": "<from>",
"to": "<to>",
"units": [
{
"unit": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"display_name": "<display_name>",
"kind": "<kind>",
"unit_type": "<unit_type>",
"user_id": "f73aee0f-0000-4000-8000-d0c5000000f7",
"colour": "<colour>",
"photo_url": "https://example.com/image.jpg",
"sort": 1,
"bookable": true,
"online_bookable": true,
"is_active": true
},
"appointments": [
{
"id": "00000d1b-0000-4000-8000-d0c500000000",
"customer_id": "96607d1c-0000-4000-8000-d0c500000096",
"guest_name": "<guest_name>",
"guest_email": "alex@example.com",
"guest_phone": "+44 7700 900123",
"service_id": "993232e5-0000-4000-8000-d0c500000099",
"starts_at": "<starts_at>",
"ends_at": "<ends_at>",
"status": "<status>",
"payment_status": "<payment_status>",
"price_pence": 1500,
"deposit_pence": 1500,
"balance_pence": 1500,
"currency": "GBP",
"party_size": 1,
"children": 1,
"high_chairs": 1,
"preferred_area_id": "afa05c4f-0000-4000-8000-d0c5000000af",
"preferred_area_name": "<preferred_area_name>",
"source": "<source>",
"is_override": true,
"checked_in_at": "<checked_in_at>",
"completed_at": "<completed_at>",
"cancelled_at": "<cancelled_at>"
}
],
"holds": [
{
"id": "00000d1b-0000-4000-8000-d0c500000000",
"starts_at": "<starts_at>",
"ends_at": "<ends_at>",
"expires_at": "<expires_at>"
}
],
"time_off": [
{
"id": "00000d1b-0000-4000-8000-d0c500000000",
"kind": "<kind>",
"starts_at": "<starts_at>",
"ends_at": "<ends_at>",
"reason": "Added at the front desk"
}
]
}
]
}The merchant rail: every waiting + called walk-in for the AUTHENTICATED store, in join order, each waiting entry carrying its DERIVED position, honest wait, and the earliest-free chair the estimate would assign it (the default assign target). Store-wide and business-authenticated, the customer status read can only ever see one own entry. Business-auth + the paid can_use_bookings entitlement.
The store whose queue to read. Must be the authenticated store.
'appointments_only' | 'walk_ins_only' | 'mixed', whether the store even takes walk-ins.
The hold-after-call window, for the rail's countdown on called entries.
Every waiting + called entry in join order. Waiting entries carry a derived position + wait + suggested chair; called entries carry called_at.
Present on the merchant surface; omitted from the customer surface.
The service the walk-in asked for, shapes the wait estimate and the assigned duration.
A chair/stylist the walk-in asked for, if any.
'waiting' | 'called' | 'assigned' | 'completed' | 'left' | 'no_show' | 'converted_to_appointment'.
ISO instant they joined the queue (the join order that derives position).
ISO instant they were called, if they have been.
The wait we quoted AT JOIN, snapshotted, so a later dispute is answerable.
Set once assigned, the booking the entry converted into.
DERIVED 1-based place in line. Null once called/assigned/left, a stored position is a second source of truth that drifts.
DERIVED minutes until a chair is expected free for this person, from the live blocks. Null when not waiting.
DERIVED ISO instant a chair is expected free for this person. Null when not waiting.
The earliest-free chair the estimate would hand this person, the rail's default assign target. Null when not waiting.
curl -G "https://www.membber.com/api/v1/bookings/queue" \
-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/bookings/queue", {
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.listStoreWalkInQueue(
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066")
).ok.body.json
print(response){
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"walkin_mode": "<walkin_mode>",
"called_hold_minutes": 1,
"entries": [
{
"entry_id": "83353c08-0000-4000-8000-d0c500000083",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"customer_id": "96607d1c-0000-4000-8000-d0c500000096",
"guest_name": "<guest_name>",
"guest_phone": "+44 7700 900123",
"service_id": "993232e5-0000-4000-8000-d0c500000099",
"preferred_unit_id": "cb0a9bf8-0000-4000-8000-d0c5000000cb",
"party_size": 1,
"status": "<status>",
"joined_at": "<joined_at>",
"called_at": "<called_at>",
"quoted_wait_min": 1,
"appointment_id": "0d727dbb-0000-4000-8000-d0c50000000d",
"queue_position": 1,
"expected_wait_min": 1,
"expected_start": "<expected_start>",
"suggested_unit_id": "28cd789a-0000-4000-8000-d0c500000028"
}
]
}Seats a walk-in at a chair/stylist/table starting now(), which BOOKS A REAL APPOINTMENT through the single booking authority, the queue never writes its own booking, so a walk-in and an online customer can never both take one chair. A lost race returns STALE_SLOT and the walk-in KEEPS THEIR PLACE (they are not dropped). Idempotent: a second assign returns the same appointment, never a double-book. With override: true the merchant force-seats a squeeze-in the authority would refuse. Both entry and unit must belong to the authenticated store. Business-auth + can_use_bookings. No money moves (deposits are Stage 6).
The authenticated store the entry + unit belong to.
The walk-in to seat. Must belong to the store.
The chair/stylist/table to seat them at, starting now(). Must belong to the store.
Override the assigned length. Defaults to the service duration (or 30 min).
FORCE-SEAT even where the availability authority would refuse (a squeeze-in). The merchant-only power, the block is exempt from the double-booking guard. Defaults to false.
The booking the walk-in became, created through the ONE booking authority, source "walk_in".
false when the entry was already assigned, an idempotent no-op returning the same appointment.
curl -X POST "https://www.membber.com/api/v1/bookings/queue/assign" \
-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",
"entry_id": "83353c08-0000-4000-8000-d0c500000083",
"unit_id": "eeebf9b6-0000-4000-8000-d0c5000000ee"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/bookings/queue/assign", {
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
entry_id: "83353c08-0000-4000-8000-d0c500000083",
unit_id: "eeebf9b6-0000-4000-8000-d0c5000000ee"
},
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.assignWalkInQueueEntry(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
entryId: "83353c08-0000-4000-8000-d0c500000083",
unitId: "eeebf9b6-0000-4000-8000-d0c5000000ee"
))
).ok.body.json
print(response){
"entry_id": "83353c08-0000-4000-8000-d0c500000083",
"appointment_id": "0d727dbb-0000-4000-8000-d0c50000000d",
"assigned": true
}Marks a WAITING walk-in as called and starts the hold-after-call countdown (they get "you're up", the shop holds their place for the policy window). "Call the next" is simply calling the first waiting entry the list returned. Single-winner and idempotent, a second call is a no-op returning the original called_at; an entry that is not waiting is refused. The entry must belong to the authenticated store. Business-auth + can_use_bookings.
The authenticated store the entry belongs to.
The entry to call. "Call the next" is the first waiting entry from the list. Must belong to the store.
false when it was already called, an idempotent no-op.
How long they are held before they lose their place.
ISO instant they were called (the existing time on a no-op re-call).
curl -X POST "https://www.membber.com/api/v1/bookings/queue/call" \
-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",
"entry_id": "83353c08-0000-4000-8000-d0c500000083"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/bookings/queue/call", {
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
entry_id: "83353c08-0000-4000-8000-d0c500000083"
},
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.callWalkInQueueEntry(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
entryId: "83353c08-0000-4000-8000-d0c500000083"
))
).ok.body.json
print(response){
"entry_id": "83353c08-0000-4000-8000-d0c500000083",
"called": true,
"hold_minutes": 1,
"called_at": "<called_at>"
}Marks a queue entry left or no_show, the merchant side of clearing the rail when a called walk-in does not appear or gives up. "left" and "no_show" are kept distinct for the record. Idempotent, removing an already-ended entry is a no-op. The entry must belong to the authenticated store. Business-auth + can_use_bookings. No fee (queue-side fees never exist in v1, no consent is captured at join).
The authenticated store the entry belongs to.
The entry to remove. Must belong to the store.
Why it is leaving the queue: 'left' (they gave up / no-answer) or 'no_show' (called and did not appear). Distinct outcomes for the record.
leftno_showThe terminal status the entry now holds.
true when the entry had already ended, an idempotent no-op.
curl -X POST "https://www.membber.com/api/v1/bookings/queue/remove" \
-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",
"entry_id": "83353c08-0000-4000-8000-d0c500000083",
"status": "left"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/bookings/queue/remove", {
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
entry_id: "83353c08-0000-4000-8000-d0c500000083",
status: "left"
},
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.removeWalkInQueueEntry(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
entryId: "83353c08-0000-4000-8000-d0c500000083",
status: .left
))
).ok.body.json
print(response){
"entry_id": "83353c08-0000-4000-8000-d0c500000083",
"status": "<status>",
"noop": true
}Every service period (Lunch/Dinner/…) for the authenticated store. Disabled periods are excluded unless asked for.
Include disabled periods. Defaults to active only.
The week this period is served. Omit a day to close it. e.g. {"friday":{"open":"18:00","close":"22:00"}}.
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
curl -G "https://www.membber.com/api/v1/bookings/service-periods" \
-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/bookings/service-periods", {
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.listServicePeriods(
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066")
).ok.body.json
print(response){
"periods": [
{
"id": "00000d1b-0000-4000-8000-d0c500000000",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"name": "Example name",
"sort": 1,
"week": {
"sunday": {
"open": "<open>",
"close": "<close>"
},
"monday": {
"open": "<open>",
"close": "<close>"
},
"tuesday": {
"open": "<open>",
"close": "<close>"
},
"wednesday": {
"open": "<open>",
"close": "<close>"
},
"thursday": {
"open": "<open>",
"close": "<close>"
},
"friday": {
"open": "<open>",
"close": "<close>"
},
"saturday": {
"open": "<open>",
"close": "<close>"
}
},
"last_seating_offset_min": 1,
"is_active": true
}
]
}Adds a named service period to the authenticated store. Send a `client_token` to make the create idempotent.
Caller-generated idempotency key, a retry with the same token returns the SAME period, never a duplicate.
What the period is called, e.g. "Lunch" or "Dinner".
Display order among the store's periods.
The week this period is served. Omit a day to close it. e.g. {"friday":{"open":"18:00","close":"22:00"}}.
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
Minutes before this period closes that the last table is seated. Overrides the store policy value for this period.
The week this period is served. Omit a day to close it. e.g. {"friday":{"open":"18:00","close":"22:00"}}.
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
curl -X POST "https://www.membber.com/api/v1/bookings/service-periods" \
-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",
"name": "Example name"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/bookings/service-periods", {
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
name: "Example name"
},
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.createServicePeriod(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
name: "Example name"
))
).ok.body.json
print(response){
"period": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"name": "Example name",
"sort": 1,
"week": {
"sunday": {
"open": "<open>",
"close": "<close>"
},
"monday": {
"open": "<open>",
"close": "<close>"
},
"tuesday": {
"open": "<open>",
"close": "<close>"
},
"wednesday": {
"open": "<open>",
"close": "<close>"
},
"thursday": {
"open": "<open>",
"close": "<close>"
},
"friday": {
"open": "<open>",
"close": "<close>"
},
"saturday": {
"open": "<open>",
"close": "<close>"
}
},
"last_seating_offset_min": 1,
"is_active": true
}
}Hard-deletes a service period (config only, no appointment references it). Idempotent: deleting one already gone is a no-op. To keep it but stop serving it, PATCH is_active=false instead.
false when there was nothing to remove (idempotent).
curl -X DELETE "https://www.membber.com/api/v1/bookings/service-periods/19dd12bc-0000-4000-8000-d0c500000019?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/bookings/service-periods/{periodId}", {
params: { path: { periodId: "19dd12bc-0000-4000-8000-d0c500000019" }, 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.deleteServicePeriod(
path: .init(periodId: "19dd12bc-0000-4000-8000-d0c500000019"),
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066")
).ok.body.json
print(response){
"period_id": "21c59859-0000-4000-8000-d0c500000021",
"removed": true
}Partial update of a service period the authenticated store owns. A window change shapes FUTURE availability only.
What the period is called, e.g. "Lunch" or "Dinner".
Display order among the store's periods.
The week this period is served. Omit a day to close it. e.g. {"friday":{"open":"18:00","close":"22:00"}}.
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
Minutes before this period closes that the last table is seated. Overrides the store policy value for this period.
Set false to disable this period without deleting it; true to re-enable.
The week this period is served. Omit a day to close it. e.g. {"friday":{"open":"18:00","close":"22:00"}}.
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
One serving window for one day.
When this period opens on this day (24h HH:MM).
When this period closes on this day (must be after open).
curl -X PATCH "https://www.membber.com/api/v1/bookings/service-periods/19dd12bc-0000-4000-8000-d0c500000019" \
-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/bookings/service-periods/{periodId}", {
params: { path: { periodId: "19dd12bc-0000-4000-8000-d0c500000019" } },
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.updateServicePeriod(
path: .init(periodId: "19dd12bc-0000-4000-8000-d0c500000019"),
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066"
))
).ok.body.json
print(response){
"period": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"name": "Example name",
"sort": 1,
"week": {
"sunday": {
"open": "<open>",
"close": "<close>"
},
"monday": {
"open": "<open>",
"close": "<close>"
},
"tuesday": {
"open": "<open>",
"close": "<close>"
},
"wednesday": {
"open": "<open>",
"close": "<close>"
},
"thursday": {
"open": "<open>",
"close": "<close>"
},
"friday": {
"open": "<open>",
"close": "<close>"
},
"saturday": {
"open": "<open>",
"close": "<close>"
}
},
"last_seating_offset_min": 1,
"is_active": true
}
}Every service the store offers, each with its variants and add-ons. Scoped to the authenticated store; archived services are excluded unless asked for.
The store whose catalogue to read (must be the authenticated store).
Include archived services. Defaults to active only.
Image URLs the merchant uploaded for this service.
fixedfromper_personon_premisesonlinecustomer_sitePricing/duration variants for this service.
Optional paid add-ons that extend this service.
curl -G "https://www.membber.com/api/v1/bookings/services" \
-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/bookings/services", {
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.listBookingServices(
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066")
).ok.body.json
print(response){
"services": [
{
"id": "00000d1b-0000-4000-8000-d0c500000000",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"category": "<category>",
"name": "Example name",
"description": "Added at the front desk",
"images": [
"<image>"
],
"duration_min": 1,
"price_pence": 1500,
"currency": "GBP",
"price_type": "fixed",
"party_min": 1,
"party_max": 1,
"buffer_before_min": 1,
"buffer_after_min": 1,
"processing_min": 1,
"location_type": "on_premises",
"rebook_interval_days": 1,
"online_visible": true,
"sort": 1,
"is_active": true,
"variants": [
{
"id": "00000d1b-0000-4000-8000-d0c500000000",
"name": "Example name",
"duration_min": 1,
"price_pence": 1500,
"sort": 1,
"is_active": true
}
],
"addons": [
{
"id": "00000d1b-0000-4000-8000-d0c500000000",
"name": "Example name",
"extra_min": 1,
"extra_pence": 1500,
"sort": 1,
"is_active": true
}
]
}
]
}Adds a service to the authenticated store. Send a `client_token` to make the create idempotent, a retry with the same token returns the same service rather than a duplicate.
Caller-generated idempotency key. Send the same token on a retry to get the SAME service back instead of a duplicate.
What the customer books.
Uploaded image URLs.
Appointment length in minutes.
Price in the smallest currency unit.
'fixed' | 'from' | 'per_person'.
fixedfromper_personDeposit policy JSON. WRITABLE but INERT until the money stage, no charge is wired from it here.
Prep time the unit is occupied before.
Clean-up time the unit is occupied after.
on_premisesonlinecustomer_siteWhether customers can see and book this online.
Image URLs the merchant uploaded for this service.
fixedfromper_personon_premisesonlinecustomer_sitePricing/duration variants for this service.
Optional paid add-ons that extend this service.
curl -X POST "https://www.membber.com/api/v1/bookings/services" \
-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",
"name": "Example name",
"duration_min": 1
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/bookings/services", {
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
name: "Example name",
duration_min: 1
},
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.createBookingService(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
name: "Example name",
durationMin: 1
))
).ok.body.json
print(response){
"service": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"category": "<category>",
"name": "Example name",
"description": "Added at the front desk",
"images": [
"<image>"
],
"duration_min": 1,
"price_pence": 1500,
"currency": "GBP",
"price_type": "fixed",
"party_min": 1,
"party_max": 1,
"buffer_before_min": 1,
"buffer_after_min": 1,
"processing_min": 1,
"location_type": "on_premises",
"rebook_interval_days": 1,
"online_visible": true,
"sort": 1,
"is_active": true,
"variants": [
{
"id": "00000d1b-0000-4000-8000-d0c500000000",
"name": "Example name",
"duration_min": 1,
"price_pence": 1500,
"sort": 1,
"is_active": true
}
],
"addons": [
{
"id": "00000d1b-0000-4000-8000-d0c500000000",
"name": "Example name",
"extra_min": 1,
"extra_pence": 1500,
"sort": 1,
"is_active": true
}
]
}
}Partial update of a service the authenticated store owns. A price or duration change shapes FUTURE availability only, a booked appointment keeps the price and policy it was snapshotted with.
What the customer books.
Uploaded image URLs.
Appointment length in minutes.
Price in the smallest currency unit.
'fixed' | 'from' | 'per_person'.
fixedfromper_personDeposit policy JSON. WRITABLE but INERT until the money stage, no charge is wired from it here.
Prep time the unit is occupied before.
Clean-up time the unit is occupied after.
on_premisesonlinecustomer_siteWhether customers can see and book this online.
Set true to RESTORE an archived service (the inverse of /archive); false archives it. Archiving never deletes, existing appointments keep their snapshot either way.
Image URLs the merchant uploaded for this service.
fixedfromper_personon_premisesonlinecustomer_sitePricing/duration variants for this service.
Optional paid add-ons that extend this service.
curl -X PATCH "https://www.membber.com/api/v1/bookings/services/f46cf6b0-0000-4000-8000-d0c5000000f4" \
-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/bookings/services/{serviceId}", {
params: { path: { serviceId: "f46cf6b0-0000-4000-8000-d0c5000000f4" } },
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.updateBookingService(
path: .init(serviceId: "f46cf6b0-0000-4000-8000-d0c5000000f4"),
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066"
))
).ok.body.json
print(response){
"service": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"category": "<category>",
"name": "Example name",
"description": "Added at the front desk",
"images": [
"<image>"
],
"duration_min": 1,
"price_pence": 1500,
"currency": "GBP",
"price_type": "fixed",
"party_min": 1,
"party_max": 1,
"buffer_before_min": 1,
"buffer_after_min": 1,
"processing_min": 1,
"location_type": "on_premises",
"rebook_interval_days": 1,
"online_visible": true,
"sort": 1,
"is_active": true,
"variants": [
{
"id": "00000d1b-0000-4000-8000-d0c500000000",
"name": "Example name",
"duration_min": 1,
"price_pence": 1500,
"sort": 1,
"is_active": true
}
],
"addons": [
{
"id": "00000d1b-0000-4000-8000-d0c500000000",
"name": "Example name",
"extra_min": 1,
"extra_pence": 1500,
"sort": 1,
"is_active": true
}
]
}
}Adds an add-on to a service the authenticated store owns. Idempotent per `client_token`.
Caller-generated idempotency key. Send the same token on a retry to get the SAME service back instead of a duplicate.
Extra minutes this add-on adds to the appointment.
Extra charge in the smallest currency unit (INERT until the money stage).
curl -X POST "https://www.membber.com/api/v1/bookings/services/f46cf6b0-0000-4000-8000-d0c5000000f4/addons" \
-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",
"name": "Example name"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/bookings/services/{serviceId}/addons", {
params: { path: { serviceId: "f46cf6b0-0000-4000-8000-d0c5000000f4" } },
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
name: "Example name"
},
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.createBookingServiceAddon(
path: .init(serviceId: "f46cf6b0-0000-4000-8000-d0c5000000f4"),
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
name: "Example name"
))
).ok.body.json
print(response){
"addon": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"name": "Example name",
"extra_min": 1,
"extra_pence": 1500,
"sort": 1,
"is_active": true
}
}Partial update of an add-on belonging to a service the authenticated store owns.
Set false to archive this add-on.
curl -X PATCH "https://www.membber.com/api/v1/bookings/services/f46cf6b0-0000-4000-8000-d0c5000000f4/addons/bb965b7b-0000-4000-8000-d0c5000000bb" \
-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/bookings/services/{serviceId}/addons/{addonId}", {
params: { path: { serviceId: "f46cf6b0-0000-4000-8000-d0c5000000f4", addonId: "bb965b7b-0000-4000-8000-d0c5000000bb" } },
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.updateBookingServiceAddon(
path: .init(serviceId: "f46cf6b0-0000-4000-8000-d0c5000000f4", addonId: "bb965b7b-0000-4000-8000-d0c5000000bb"),
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066"
))
).ok.body.json
print(response){
"addon": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"name": "Example name",
"extra_min": 1,
"extra_pence": 1500,
"sort": 1,
"is_active": true
}
}Sets the service inactive so it stops appearing in availability, but NEVER deletes it, every existing appointment keeps its snapshotted price/duration and its link to the service. Idempotent.
true once inactive (idempotent, archiving twice is a no-op).
curl -X POST "https://www.membber.com/api/v1/bookings/services/f46cf6b0-0000-4000-8000-d0c5000000f4/archive" \
-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/bookings/services/{serviceId}/archive", {
params: { path: { serviceId: "f46cf6b0-0000-4000-8000-d0c5000000f4" } },
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.archiveBookingService(
path: .init(serviceId: "f46cf6b0-0000-4000-8000-d0c5000000f4"),
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066"
))
).ok.body.json
print(response){
"service_id": "993232e5-0000-4000-8000-d0c500000099",
"archived": true
}Adds a variant to a service the authenticated store owns. Idempotent per `client_token`.
Caller-generated idempotency key. Send the same token on a retry to get the SAME service back instead of a duplicate.
Overrides the service duration for this variant.
curl -X POST "https://www.membber.com/api/v1/bookings/services/f46cf6b0-0000-4000-8000-d0c5000000f4/variants" \
-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",
"name": "Example name"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/bookings/services/{serviceId}/variants", {
params: { path: { serviceId: "f46cf6b0-0000-4000-8000-d0c5000000f4" } },
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
name: "Example name"
},
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.createBookingServiceVariant(
path: .init(serviceId: "f46cf6b0-0000-4000-8000-d0c5000000f4"),
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
name: "Example name"
))
).ok.body.json
print(response){
"variant": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"name": "Example name",
"duration_min": 1,
"price_pence": 1500,
"sort": 1,
"is_active": true
}
}Partial update of a variant belonging to a service the authenticated store owns.
Set false to archive this variant.
curl -X PATCH "https://www.membber.com/api/v1/bookings/services/f46cf6b0-0000-4000-8000-d0c5000000f4/variants/fb1b0c80-0000-4000-8000-d0c5000000fb" \
-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/bookings/services/{serviceId}/variants/{variantId}", {
params: { path: { serviceId: "f46cf6b0-0000-4000-8000-d0c5000000f4", variantId: "fb1b0c80-0000-4000-8000-d0c5000000fb" } },
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.updateBookingServiceVariant(
path: .init(serviceId: "f46cf6b0-0000-4000-8000-d0c5000000f4", variantId: "fb1b0c80-0000-4000-8000-d0c5000000fb"),
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066"
))
).ok.body.json
print(response){
"variant": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"name": "Example name",
"duration_min": 1,
"price_pence": 1500,
"sort": 1,
"is_active": true
}
}Writes `stores.timezone` after validating it is a real IANA timezone. This is the anchor every day boundary, hour label and rollup in the bookings vertical reads, so an invalid value is refused rather than silently skewing a diary. Idempotent.
An IANA timezone name, e.g. "Europe/London". Validated against the runtime before it is written.
curl -X PUT "https://www.membber.com/api/v1/bookings/store-timezone" \
-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",
"timezone": "Europe/London"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.PUT("/api/v1/bookings/store-timezone", {
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
timezone: "Europe/London"
},
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.setStoreTimezone(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
timezone: "Europe/London"
))
).ok.body.json
print(response){
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"timezone": "Europe/London"
}The customer-facing view of a store's bookable services and people, the read the Book flow starts from, and the only way to obtain the `service_id` that availability requires. Public, because showing what a shop offers must not require an account. Returns only what is genuinely offered online: archived and staff-only services are absent, and so is anyone who is not taking online bookings.
The store whose bookable offering to read.
Bookable online, in the merchant's own order.
The merchant's own photos. Empty is a real state, the client draws its designed blank, never a stock photo.
What the customer is told the appointment lasts. Buffers are NEVER exposed, they are the shop's business, not the customer's.
'from' when performers price it differently, the exact figure locks at the person step.
fixedfromper_personSmallest party this service takes. 1 for a normal appointment.
Largest party bookable online. Above it, the client offers "message the restaurant".
Who picks the table for a tables store: 0 the house assigns, 1 the guest picks, 2 reserved. From booking_policies.table_choice_level (store-level).
Who can be booked, in the merchant's own order, the same order the diary columns use.
The bookable unit id, what `availability` and the booking call take as `unit_id`.
Per-person price/duration for a service, where it differs from the service default. Resolved so the confirm card can show the TRUE figure for this person.
The venue's dining areas, in the merchant's order. Empty unless it is a tables store with areas.
The area name the diner picks, e.g. "Window", "Main room", "Bar".
Optional one line, e.g. "Heated and covered".
False when the store is set up but PAUSED. The client shows one calm line rather than an empty slot grid pretending to be availability.
curl -G "https://www.membber.com/api/v1/bookings/storefront" \
--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/bookings/storefront", {
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.getBookingStorefront(
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066")
).ok.body.json
print(response){
"services": [
{
"id": "00000d1b-0000-4000-8000-d0c500000000",
"name": "Example name",
"description": "Added at the front desk",
"images": [
"<image>"
],
"duration_min": 1,
"price_pence": 1500,
"currency": "GBP",
"price_type": "fixed",
"category": "<category>",
"unit_ids": [
"eeebf9b6-0000-4000-8000-d0c5000000ee"
],
"sort": 1,
"party_min": 1,
"party_max": 1,
"seat_choice_level": 1
}
],
"people": [
{
"id": "00000d1b-0000-4000-8000-d0c500000000",
"display_name": "<display_name>",
"photo_url": "https://example.com/image.jpg",
"sort": 1,
"overrides": [
{
"service_id": "993232e5-0000-4000-8000-d0c500000099",
"duration_min": 1,
"price_pence": 1500
}
]
}
],
"areas": [
{
"id": "00000d1b-0000-4000-8000-d0c500000000",
"name": "Example name",
"sort": 1,
"description": "Added at the front desk"
}
],
"accepting": true
}Every bookable unit for the authenticated store, each carrying which services it offers. Archived units excluded unless asked for.
personspaceassetFree-text sub-type (e.g. "chair", "room").
The staff member this unit represents, when kind = person.
Diary column colour.
Whether the unit can hold appointments at all.
Whether customers can book it online (vs staff-only).
Which services this unit offers, with any per-unit overrides.
curl -G "https://www.membber.com/api/v1/bookings/units" \
-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/bookings/units", {
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.listBookableUnits(
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066")
).ok.body.json
print(response){
"units": [
{
"id": "00000d1b-0000-4000-8000-d0c500000000",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"kind": "person",
"unit_type": "<unit_type>",
"user_id": "f73aee0f-0000-4000-8000-d0c5000000f7",
"display_name": "<display_name>",
"bio": "<bio>",
"photo_url": "https://example.com/image.jpg",
"colour": "<colour>",
"bookable": true,
"online_bookable": true,
"sort": 1,
"is_active": true,
"services": [
{
"service_id": "993232e5-0000-4000-8000-d0c500000099",
"duration_override_min": 1,
"price_override_pence": 1500
}
]
}
]
}Adds a person / space / asset to the authenticated store. Idempotent per `client_token`.
Caller-generated idempotency key, a retry with the same token returns the SAME unit, never a duplicate.
'person' | 'space' | 'asset'. Defaults to 'person'.
personspaceassetOnly permitted when kind = person (the DB enforces this).
What the diary column is labelled.
personspaceassetFree-text sub-type (e.g. "chair", "room").
The staff member this unit represents, when kind = person.
Diary column colour.
Whether the unit can hold appointments at all.
Whether customers can book it online (vs staff-only).
Which services this unit offers, with any per-unit overrides.
curl -X POST "https://www.membber.com/api/v1/bookings/units" \
-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",
"display_name": "<display_name>"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/bookings/units", {
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
display_name: "<display_name>"
},
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.createBookableUnit(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
displayName: "<display_name>"
))
).ok.body.json
print(response){
"unit": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"kind": "person",
"unit_type": "<unit_type>",
"user_id": "f73aee0f-0000-4000-8000-d0c5000000f7",
"display_name": "<display_name>",
"bio": "<bio>",
"photo_url": "https://example.com/image.jpg",
"colour": "<colour>",
"bookable": true,
"online_bookable": true,
"sort": 1,
"is_active": true,
"services": [
{
"service_id": "993232e5-0000-4000-8000-d0c500000099",
"duration_override_min": 1,
"price_override_pence": 1500
}
]
}
}Partial update of a unit the authenticated store owns. Archiving (is_active=false) keeps existing appointments intact.
'person' | 'space' | 'asset'. Defaults to 'person'.
personspaceassetOnly permitted when kind = person (the DB enforces this).
What the diary column is labelled.
Set false to archive the unit (existing appointments are honoured).
personspaceassetFree-text sub-type (e.g. "chair", "room").
The staff member this unit represents, when kind = person.
Diary column colour.
Whether the unit can hold appointments at all.
Whether customers can book it online (vs staff-only).
Which services this unit offers, with any per-unit overrides.
curl -X PATCH "https://www.membber.com/api/v1/bookings/units/cde691bf-0000-4000-8000-d0c5000000cd" \
-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/bookings/units/{unitId}", {
params: { path: { unitId: "cde691bf-0000-4000-8000-d0c5000000cd" } },
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.updateBookableUnit(
path: .init(unitId: "cde691bf-0000-4000-8000-d0c5000000cd"),
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066"
))
).ok.body.json
print(response){
"unit": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"kind": "person",
"unit_type": "<unit_type>",
"user_id": "f73aee0f-0000-4000-8000-d0c5000000f7",
"display_name": "<display_name>",
"bio": "<bio>",
"photo_url": "https://example.com/image.jpg",
"colour": "<colour>",
"bookable": true,
"online_bookable": true,
"sort": 1,
"is_active": true,
"services": [
{
"service_id": "993232e5-0000-4000-8000-d0c500000099",
"duration_override_min": 1,
"price_override_pence": 1500
}
]
}
}Records that a unit offers a service (with optional per-unit duration/price overrides). Upsert on the (unit, service) pair, so it is idempotent, re-sending it converges rather than duplicating. Both the unit and the service must belong to the authenticated store.
The service this unit should offer (must belong to the same store).
Per-unit duration override.
Per-unit price override.
curl -X POST "https://www.membber.com/api/v1/bookings/units/cde691bf-0000-4000-8000-d0c5000000cd/services" \
-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",
"service_id": "993232e5-0000-4000-8000-d0c500000099"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/bookings/units/{unitId}/services", {
params: { path: { unitId: "cde691bf-0000-4000-8000-d0c5000000cd" } },
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
service_id: "993232e5-0000-4000-8000-d0c500000099"
},
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.setBookableUnitService(
path: .init(unitId: "cde691bf-0000-4000-8000-d0c5000000cd"),
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
serviceId: "993232e5-0000-4000-8000-d0c500000099"
))
).ok.body.json
print(response){
"unit_id": "eeebf9b6-0000-4000-8000-d0c5000000ee",
"service_id": "993232e5-0000-4000-8000-d0c500000099"
}Removes the (unit, service) mapping. Idempotent, removing one that is already gone is a no-op.
false when there was nothing to remove (idempotent).
curl -X DELETE "https://www.membber.com/api/v1/bookings/units/cde691bf-0000-4000-8000-d0c5000000cd/services/f46cf6b0-0000-4000-8000-d0c5000000f4?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/bookings/units/{unitId}/services/{serviceId}", {
params: { path: { unitId: "cde691bf-0000-4000-8000-d0c5000000cd", serviceId: "f46cf6b0-0000-4000-8000-d0c5000000f4" }, 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.removeBookableUnitService(
path: .init(unitId: "cde691bf-0000-4000-8000-d0c5000000cd", serviceId: "f46cf6b0-0000-4000-8000-d0c5000000f4"),
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066")
).ok.body.json
print(response){
"unit_id": "eeebf9b6-0000-4000-8000-d0c5000000ee",
"service_id": "993232e5-0000-4000-8000-d0c500000099",
"removed": true
}The working intervals for a unit the authenticated store owns. Multiple rows for one weekday ARE the intervals; the gaps ARE the breaks.
0 = Sunday … 6 = Saturday.
Minutes past midnight the interval starts.
Minutes past midnight the interval ends (must be after start).
True for an interval that runs past midnight.
YYYY-MM-DD the interval starts applying (null = always).
YYYY-MM-DD the interval stops applying (null = open-ended).
curl -G "https://www.membber.com/api/v1/bookings/units/cde691bf-0000-4000-8000-d0c5000000cd/working-patterns" \
-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/bookings/units/{unitId}/working-patterns", {
params: { path: { unitId: "cde691bf-0000-4000-8000-d0c5000000cd" }, 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.listUnitWorkingPatterns(
path: .init(unitId: "cde691bf-0000-4000-8000-d0c5000000cd"),
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066")
).ok.body.json
print(response){
"unit_id": "eeebf9b6-0000-4000-8000-d0c5000000ee",
"patterns": [
{
"weekday": 0,
"start_minute": 0,
"end_minute": 1,
"spans_midnight": true,
"effective_from": "<effective_from>",
"effective_to": "<effective_to>",
"id": "00000d1b-0000-4000-8000-d0c500000000"
}
]
}Swaps the unit's ENTIRE working pattern for the set you send, atomically (the old rows and the new ones never coexist, and a half-applied pattern is impossible). Idempotent, replacing with the same set converges. The unit must belong to the authenticated store.
The COMPLETE new set of intervals for this unit. Send an empty array to clear the pattern.
0 = Sunday … 6 = Saturday.
Minutes past midnight the interval starts.
Minutes past midnight the interval ends (must be after start).
True for an interval that runs past midnight.
YYYY-MM-DD the interval starts applying (null = always).
YYYY-MM-DD the interval stops applying (null = open-ended).
0 = Sunday … 6 = Saturday.
Minutes past midnight the interval starts.
Minutes past midnight the interval ends (must be after start).
True for an interval that runs past midnight.
YYYY-MM-DD the interval starts applying (null = always).
YYYY-MM-DD the interval stops applying (null = open-ended).
curl -X PUT "https://www.membber.com/api/v1/bookings/units/cde691bf-0000-4000-8000-d0c5000000cd/working-patterns" \
-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",
"patterns": [
{
"weekday": 0,
"start_minute": 0,
"end_minute": 1
}
]
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.PUT("/api/v1/bookings/units/{unitId}/working-patterns", {
params: { path: { unitId: "cde691bf-0000-4000-8000-d0c5000000cd" } },
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
patterns: [
{
weekday: 0,
start_minute: 0,
end_minute: 1
}
]
},
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.replaceUnitWorkingPatterns(
path: .init(unitId: "cde691bf-0000-4000-8000-d0c5000000cd"),
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
patterns: [.init(
weekday: 0,
startMinute: 0,
endMinute: 1
)]
))
).ok.body.json
print(response){
"unit_id": "eeebf9b6-0000-4000-8000-d0c5000000ee",
"patterns": [
{
"weekday": 0,
"start_minute": 0,
"end_minute": 1,
"spans_midnight": true,
"effective_from": "<effective_from>",
"effective_to": "<effective_to>",
"id": "00000d1b-0000-4000-8000-d0c500000000"
}
]
}