6 operations. Every schema and example on this page is generated from the platform contract.
Always scoped to the signed-in customer, ownership is part of the query, never a filter applied after fetching. There is no way to ask for somebody else's bookings.
Narrow to one store. Omit for every store this customer has booked with.
Include appointments that have already happened. Defaults to upcoming only.
Every amount carries its currency, never inferred from the caller.
curl -G "https://www.membber.com/api/v1/appointments" \
-H "Authorization: Bearer $MEMBBER_TOKEN"import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.GET("/api/v1/appointments");
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.listAppointments().ok.body.json
print(response){
"appointments": [
{
"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",
"starts_at": "<starts_at>",
"ends_at": "<ends_at>",
"status": "<status>",
"payment_status": "<payment_status>",
"price_pence": 1500,
"deposit_pence": 1500,
"currency": "GBP",
"party_size": 1
}
]
}Converts a hold into a booking IN PLACE, so the slot is never free for an instant between paying and being booked. Double-booking is prevented by a database constraint rather than a check, so a lost race returns STALE_SLOT and nothing is written. Price and policy are SNAPSHOTTED onto the appointment: a later change to either never rewrites what the customer agreed to. Send an Idempotency-Key, retrying is safe and will not produce a second booking.
The store being booked.
The staff member / room / table.
The service booked. Its price and duration are SNAPSHOTTED onto the appointment.
ISO instant the appointment starts.
ISO instant the appointment ends.
The hold taken for this slot. Strongly recommended: without it the slot is unprotected between the availability read and this call.
Required when booking without a signed-in customer, somebody has to be reachable.
Who the appointment is for, when there is no customer record.
People in the party. 1 for a normal appointment.
Agreed price in the smallest currency unit. Snapshotted, a later price change never rewrites it.
Deposit due now. Greater than zero leaves the appointment pending_payment until it is settled.
ISO currency for the amounts above. Defaults to the store's.
The booking.
'confirmed', or 'pending_payment' when a deposit is still due.
ISO instant it starts.
ISO instant it ends, what the customer was told, not how long the unit is occupied.
'collect_deposit' when a deposit must be taken before this booking is safe; null when nothing is owed now.
curl -X POST "https://www.membber.com/api/v1/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.raw.POST("/api/v1/appointments", {
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.createAppointment(
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": "0d727dbb-0000-4000-8000-d0c50000000d",
"status": "<status>",
"starts_at": "<starts_at>",
"ends_at": "<ends_at>",
"payment_next_action": "<payment_next_action>"
}Computed from the unit's working patterns in the STORE's timezone, at the store's own slot increment, honouring lead time and booking horizon. Every slot returned has been checked against the same single authority the booking call uses, so a slot listed here is genuinely bookable, but it is still a snapshot: another customer can take one between this read and your booking, which is exactly what STALE_SLOT reports.
The store to read availability for.
The service being booked, its duration and buffers shape the slots.
First date to search, YYYY-MM-DD, in the STORE's timezone.
Last date to search, YYYY-MM-DD. Clamped to the store's booking horizon.
Restrict to one staff member / room / table. Omit to see every unit that offers the service.
Every slot that is genuinely bookable right now, soonest first.
Which staff member / room / table this slot belongs to.
ISO instant the APPOINTMENT starts (what the customer is told).
ISO instant the APPOINTMENT ends. The unit may be occupied longer if the service has buffers.
When this list was computed. A slot list is a snapshot of a moving thing, compare this before committing, and expect STALE_SLOT if it is old.
curl -G "https://www.membber.com/api/v1/appointments/availability" \
--data-urlencode "store_id=6659c139-0000-4000-8000-d0c500000066" \
--data-urlencode "service_id=993232e5-0000-4000-8000-d0c500000099" \
--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/appointments/availability", {
params: { query: { store_id: "6659c139-0000-4000-8000-d0c500000066", service_id: "993232e5-0000-4000-8000-d0c500000099", 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.getAppointmentAvailability(
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066", serviceId: "993232e5-0000-4000-8000-d0c500000099", fromDate: "<from_date>", toDate: "<to_date>")
).ok.body.json
print(response){
"slots": [
{
"unit_id": "eeebf9b6-0000-4000-8000-d0c5000000ee",
"starts_at": "<starts_at>",
"ends_at": "<ends_at>"
}
],
"generated_at": "<generated_at>"
}Call without `confirm` to see exactly what cancelling costs before committing to it, then again with `confirm: true`. The fee is evaluated against the policy SNAPSHOTTED on the booking, so a shop tightening its terms next week cannot retroactively charge someone who booked under the old ones. Idempotent, cancelling twice is a no-op, never a second charge.
The appointment to cancel.
Why, for the record and the merchant.
false/omitted returns a PREVIEW of what cancelling costs and changes nothing. true performs it. A fee discovered AFTER the fact is the most complained-about thing in booking apps.
false when this was a preview.
What cancelling costs, from the policy the customer AGREED to, not the shop's current one.
What comes back of anything already paid. Never more than was taken.
true when it is still early enough to cancel free.
The window that applied to THIS booking.
curl -X POST "https://www.membber.com/api/v1/appointments/cancel" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"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.raw.POST("/api/v1/appointments/cancel", {
body: {
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.cancelAppointment(
body: .json(.init(
appointmentId: "0d727dbb-0000-4000-8000-d0c50000000d"
))
).ok.body.json
print(response){
"appointment_id": "0d727dbb-0000-4000-8000-d0c50000000d",
"cancelled": true,
"fee_pence": 1500,
"refund_pence": 1500,
"currency": "GBP",
"within_free_window": true,
"cancel_window_hours": 1
}Holds a slot so nobody else can take it mid-payment. Ten minutes by default, long enough for a card security check, short enough that an abandoned checkout does not sterilise a busy slot. Idempotent per hold_token. Returns STALE_SLOT if the slot went first, or HOLD_LIMIT (with Retry-After) if this unit already has too many live holds for that day, which is the anti-squatting guard.
The staff member / room / table to reserve.
ISO instant the appointment would start.
ISO instant the appointment would end.
Caller-generated idempotency key. The SAME token returns the SAME hold rather than taking a second slot, so a retry or a double tap can never consume two times.
The reservation.
ISO instant the hold lapses, 10 minutes by default (founder decision D10). Show this to the customer rather than guessing it.
true when this token already had a live hold and it was returned unchanged (idempotent replay).
curl -X POST "https://www.membber.com/api/v1/appointments/holds" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"unit_id": "eeebf9b6-0000-4000-8000-d0c5000000ee",
"starts_at": "<starts_at>",
"ends_at": "<ends_at>",
"hold_token": "<hold_token>"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/appointments/holds", {
body: {
unit_id: "eeebf9b6-0000-4000-8000-d0c5000000ee",
starts_at: "<starts_at>",
ends_at: "<ends_at>",
hold_token: "<hold_token>"
},
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.createAppointmentHold(
body: .json(.init(
unitId: "eeebf9b6-0000-4000-8000-d0c5000000ee",
startsAt: "<starts_at>",
endsAt: "<ends_at>",
holdToken: "<hold_token>"
))
).ok.body.json
print(response){
"hold_id": "4112d09b-0000-4000-8000-d0c500000041",
"expires_at": "<expires_at>",
"reused": true
}Secures the NEW slot before releasing the OLD one, so if the destination has gone the original booking survives untouched, you are never left with nothing. Returns STALE_SLOT if the new time went first, or OUTSIDE_POLICY_WINDOW if it is too close to the appointment to change online.
The appointment to move.
Where it is moving to, may be the same staff member / room / table.
ISO instant of the new start.
ISO instant of the new end.
A hold taken on the NEW slot. Recommended for the same reason as booking: without it the destination is unprotected.
true when the booking was nudged within the same unit (the block moved rather than being re-taken).
curl -X POST "https://www.membber.com/api/v1/appointments/reschedule" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"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.raw.POST("/api/v1/appointments/reschedule", {
body: {
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.rescheduleAppointment(
body: .json(.init(
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",
"starts_at": "<starts_at>",
"ends_at": "<ends_at>",
"unit_id": "eeebf9b6-0000-4000-8000-d0c5000000ee",
"moved_in_place": true
}