API referenceBookings

Bookings

43 operations. Every schema and example on this page is generated from the platform contract.

Quick-add a booking as the merchant.

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.

Request body

store_idstring · uuidrequired
unit_idstring · uuidrequired

The staff member / room / table to book. Must belong to the authenticated store.

service_idstring · uuid, nullableoptional
starts_atstringrequired

ISO instant the appointment starts.

ends_atstringrequired

ISO instant the appointment ends.

customer_idstring · uuid, nullableoptional

An existing customer to book for.

guest_namestring, nullableoptional

Who the appointment is for, for a walk-up with no account.

guest_emailstring · email, nullableoptional

Required if there is no customer_id, someone must be reachable.

guest_phonestring, nullableoptional
party_sizeintegeroptional
price_penceintegeroptional

Agreed price, snapshotted onto the appointment.

block_starts_atstring, nullableoptional

Widen what the UNIT is occupied for (buffers), without changing what the customer is told.

block_ends_atstring, nullableoptional
join_unit_idsarray of string · uuidoptional

The OTHER tables to push together with this one, for a party too big for a single table. Front of house takes an eight over the phone the same way a guest books one online. With `override` the merchant may combine tables they have not declared, their floor, their call, otherwise the join must be declared.

overridebooleanoptional

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.

client_tokenstringoptional

Caller-generated idempotency key, a retry with the same token returns the SAME appointment, never a duplicate.

Response, 200

appointmentobjectrequired
22 child fields
idstringrequired
booking_refstring, nullablerequired

The short reference the customer quotes.

store_idstringrequired
unit_idstringrequired
service_idstring, nullablerequired
customer_idstring, nullablerequired
guest_namestring, nullablerequired
guest_emailstring, nullablerequired
guest_phonestring, nullablerequired
starts_atstringrequired
ends_atstringrequired
statusstringrequired
payment_statusstringrequired
price_pencenumberrequired
deposit_pencenumberrequired
balance_pencenumberrequired
currencystringrequired
party_sizenumberrequired
childrennumberrequired

How many of the party are children (age 12 and under). 0 for a normal appointment.

high_chairsnumberrequired

High chairs the diner asked for, so the host can fetch them.

sourcestringrequired
is_overridebooleanrequired
reusedbooleanrequired

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>"
  }'
Response, 200
{
  "appointment": {
    "id": "00000d1b-0000-4000-8000-d0c500000000",
    "booking_ref": "<booking_ref>",
    "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
}

Find a booking by the reference the caller quoted.

Resolves the short reference a diner quotes ("I booked under K4M2P7") to the one booking that carries it at the AUTHENTICATED store. References are unique per store, so the answer is one booking or NOT_FOUND, never a list and never a near miss. Input is forgiving about how the code was heard (see `ref`); the match itself is exact. This is what makes the reference usable by a phone agent rather than merely printable.

auth merchant:readop getAppointmentByReferencerate limitederrors

Parameters

store_idstring · uuidqueryrequired
refstringqueryrequired

The reference the caller said, as they said it. Case, spaces and hyphens are all forgiven, and so are the eight characters the alphabet leaves out: O and Q resolve as 0, I and L as 1, S as 5, Z as 2, B as 8 and U as V, because those are exactly the mistakes a person makes writing down a code they heard rather than saw. Anything that is still not a reference after that comes back as VALIDATION_ERROR rather than being guessed at.

Response, 200

timezonestringrequired

The store's IANA timezone, the instants above happen in it.

appointmentobjectrequired
28 child fields
idstringrequired
booking_refstringrequired
customer_idstring, nullablerequired
guest_namestring, nullablerequired
guest_emailstring, nullablerequired
guest_phonestring, nullablerequired
customer_namestring, nullablerequired

The account holder, or null for a guest booking and for a customer who has been erased.

service_idstring, nullablerequired
service_namestring, nullablerequired

What they booked, in the merchant's own words.

unit_idstringrequired
unit_namestring, nullablerequired

Which table / chair / room.

starts_atstringrequired

ISO instant it starts.

ends_atstringrequired

ISO instant it ends.

statusstringrequired
payment_statusstringrequired
price_pencenumberrequired
deposit_pencenumberrequired
balance_pencenumberrequired
currencystringrequired
party_sizenumberrequired
childrennumberrequired
high_chairsnumberrequired
preferred_area_idstring, nullablerequired
preferred_area_namestring, nullablerequired
sourcestringrequired
checked_in_atstring, nullablerequired
completed_atstring, nullablerequired
cancelled_atstring, nullablerequired
curl -G "https://www.membber.com/api/v1/bookings/appointments/by-reference" \
  -H "Authorization: Bearer $MEMBBER_TOKEN" \
  --data-urlencode "store_id=6659c139-0000-4000-8000-d0c500000066" \
  --data-urlencode "ref=<ref>"
Response, 200
{
  "timezone": "Europe/London",
  "appointment": {
    "id": "00000d1b-0000-4000-8000-d0c500000000",
    "booking_ref": "<booking_ref>",
    "customer_id": "96607d1c-0000-4000-8000-d0c500000096",
    "guest_name": "<guest_name>",
    "guest_email": "alex@example.com",
    "guest_phone": "+44 7700 900123",
    "customer_name": "Alex Example",
    "service_id": "993232e5-0000-4000-8000-d0c500000099",
    "service_name": "<service_name>",
    "unit_id": "eeebf9b6-0000-4000-8000-d0c5000000ee",
    "unit_name": "<unit_name>",
    "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>",
    "checked_in_at": "<checked_in_at>",
    "completed_at": "<completed_at>",
    "cancelled_at": "<cancelled_at>"
  }
}

Cancel an appointment as the merchant.

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).

Request body

store_idstring · uuidrequired
appointment_idstring · uuidrequired

The appointment to cancel. Must belong to the authenticated store.

reasonstring, nullableoptional

Why, for the record.

Response, 200

appointment_idstringrequired
cancelledbooleanrequired
noopbooleanrequired

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"
  }'
Response, 200
{
  "appointment_id": "0d727dbb-0000-4000-8000-d0c50000000d",
  "cancelled": true,
  "noop": true
}

Check a customer in.

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.

Request body

store_idstring · uuidrequired
appointment_idstring · uuidrequired

The appointment. Must belong to the authenticated store.

Response, 200

appointment_idstringrequired
statusstringrequired

The status the appointment now holds.

noopbooleanrequired

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"
  }'
Response, 200
{
  "appointment_id": "0d727dbb-0000-4000-8000-d0c50000000d",
  "status": "<status>",
  "noop": true
}

Complete an appointment.

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).

Request body

store_idstring · uuidrequired
appointment_idstring · uuidrequired

The appointment. Must belong to the authenticated store.

Response, 200

appointment_idstringrequired
statusstringrequired

The status the appointment now holds.

noopbooleanrequired

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"
  }'
Response, 200
{
  "appointment_id": "0d727dbb-0000-4000-8000-d0c50000000d",
  "status": "<status>",
  "noop": true
}

Record a diner's allergy on their booking.

What the host types when somebody rings up and says "my wife is coeliac", the same structured record the diner could have made themselves, stored identically, so the card at check-in reads the same either way. Store-scoped at the database layer: the RPC only acts on a booking whose store is the authenticated one. Audited, with the staff member as the actor. Idempotent on client_token. No money moves. Errors: APPOINTMENT_NOT_FOUND, INVALID_TRANSITION (cancelled, completed or no-show).

Request body

store_idstring · uuidrequired
appointment_idstring · uuidrequired

The booking. Must belong to the authenticated store, a foreign id reads as not found.

dietary_requirementsarray of enumrequired

The COMPLETE set of requirements for this booking. This REPLACES whatever stood before, send the whole set every time, never a delta. Send an empty array to say "we asked, there is nothing", which is a real and useful statement and is recorded as one.

CeleryGlutenCrustaceansEggsFishLupinMilkMolluscsMustardPeanutsSesameSoyaSulphitesTree nutsVegetarianVeganHalalKosherPescatarianDairy-freeGluten-free
notestring, nullableoptional

The detail one tap in. Omit or null to clear it. At most 280 characters, the same ceiling as a diner note.

client_tokenstring, nullableoptional

Your own id for this act. The same token twice records once and returns the same answer, so a retried tap or an offline queue flushed twice never double-writes.

Response, 200

dietary_requirementsarray of enumrequired

The requirements standing on this booking. EMPTY IS NOT "NONE", read it together with `dietary_recorded_at`, which is the only field that says whether anybody has been asked.

CeleryGlutenCrustaceansEggsFishLupinMilkMolluscsMustardPeanutsSesameSoyaSulphitesTree nutsVegetarianVeganHalalKosherPescatarianDairy-freeGluten-free
dietary_notestring, nullablerequired

The detail, in the diner's own words, "severe, airborne". Never a replacement for the list above: anything the vocabulary can name names itself, and this carries what it cannot.

dietary_recorded_atstring, nullablerequired

🔴 ISO instant somebody last stated this booking's requirements. NULL means NOBODY HAS SAID, which is NOT the same fact as "no requirements" and must never be rendered as one. A client that drops this field cannot tell a coeliac table from a table nobody asked.

dietary_recorded_byenumrequired

Who stated it. Front of house treats "the diner told us" and "we typed it up off a phone call" differently, so the provenance travels with the fact.

customermerchantagent
dietary_has_allergenbooleanrequired

True when at least one requirement is one of the FOURTEEN a UK food business must declare, rather than a preference. Computed SERVER-SIDE from the one exported decider, deliberately: a client that held its own copy of the statutory list would be a second list to drift, and the distinction is not cosmetic, "vegan" is a choice a kitchen should honour, "peanuts" is a thing that can put somebody in an ambulance, and the two must never render as the same chip.

appointment_idstringrequired
duplicatebooleanrequired

true when this client_token had already been used, so nothing new was written. Still a success.

curl -X POST "https://www.membber.com/api/v1/bookings/appointments/dietary" \
  -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",
    "dietary_requirements": [
      "Celery"
    ]
  }'
Response, 200
{
  "dietary_requirements": [
    "Celery"
  ],
  "dietary_note": "Added at the front desk",
  "dietary_recorded_at": "<dietary_recorded_at>",
  "dietary_recorded_by": "customer",
  "dietary_has_allergen": true,
  "appointment_id": "0d727dbb-0000-4000-8000-d0c50000000d",
  "duplicate": true
}

Record a no-show.

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).

Request body

store_idstring · uuidrequired
appointment_idstring · uuidrequired

The appointment. Must belong to the authenticated store.

Response, 200

appointment_idstringrequired
statusstringrequired

The status the appointment now holds.

noopbooleanrequired

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"
  }'
Response, 200
{
  "appointment_id": "0d727dbb-0000-4000-8000-d0c50000000d",
  "status": "<status>",
  "noop": true
}

Move an appointment as the merchant.

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.

Request body

store_idstring · uuidrequired
appointment_idstring · uuidrequired

The appointment to move. Must belong to the authenticated store.

unit_idstring · uuidrequired

Where it is moving to, may be the same unit. Must belong to the authenticated store.

starts_atstringrequired

ISO instant of the new start.

ends_atstringrequired

ISO instant of the new end.

block_starts_atstring, nullableoptional
block_ends_atstring, nullableoptional
overridebooleanoptional

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).

Response, 200

appointment_idstringrequired
unit_idstringrequired
starts_atstringrequired
ends_atstringrequired
moved_in_placebooleanrequired

true when the booking was nudged within the same unit.

unit_idsarray of stringoptional

Every table the booking now occupies. A joined booking moves in TIME as a whole; moving it onto a different table is refused (JOINED_BOOKING) rather than half-done.

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>"
  }'
Response, 200
{
  "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,
  "unit_ids": [
    "eeebf9b6-0000-4000-8000-d0c5000000ee"
  ]
}

Search the appointment history by person.

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.

auth merchant:readop searchAppointmentPeoplerate limitederrors

Parameters

store_idstring · uuidqueryrequired
qstringqueryrequired

Part of a name or an email address. Two characters minimum: one letter matches most of a roster.

limitintegerqueryoptional

How many people to return. Default 20.

visit_limitintegerqueryoptional

How many recent appointments per person. Default 12.

Response, 200

store_idstringrequired
timezonestringrequired

The store's IANA timezone, every instant above is anchored to it for display.

querystringrequired

The normalised needle actually searched for.

totalnumberrequired

How many people matched in total, counted BEFORE `limit`, so a client can say "20 of 34" honestly.

peoplearray of objectrequired

Most recently seen first.

14 child fields
keystringrequired

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.

customer_idstring, nullablerequired

Set only where the booking was made from an account.

namestringrequired

The most recent name this person booked under.

emailstring, nullablerequired
currencystringrequired
visitsnumberrequired

Times they were actually in the chair: completed + checked-in. A no-show is not a visit and is counted separately.

no_showsnumberrequired
cancellednumberrequired
unresolvednumberrequired

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.

first_visitstring, nullablerequired

ISO instant of their first actual visit.

last_visitstring, nullablerequired

ISO instant of their most recent actual visit, the answer to the question.

next_visitstring, nullablerequired

ISO instant of their soonest future booking, if any.

spend_pencenumberrequired

Sum of the price on visits that happened. Not a forecast, not a lifetime value.

recentarray of objectrequired

Their most recent appointments, newest first, capped by `visit_limit`.

8 child fields
idstringrequired
starts_atstringrequired

ISO instant the appointment started.

ends_atstringrequired
statusstringrequired

'completed' | 'checked_in' | 'confirmed' | 'cancelled' | 'no_show' | 'pending_payment'.

payment_statusstringrequired
price_pencenumberrequired
currencystringrequired
unit_namestring, nullablerequired

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>"
Response, 200
{
  "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>"
        }
      ]
    }
  ]
}

Record what is still owed on an appointment as taken.

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.

Request body

store_idstring · uuidrequired

Store the appointment belongs to (authorisation only, the store acted on always comes from auth).

appointment_idstring · uuidrequired

The appointment to settle. Must belong to the authenticated store.

methodenumrequired

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

Response, 200

outcomeenumrequired

'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_owed
appointment_idstringrequired
statusstringrequired

The appointment status after the call, settling completes the appointment.

payment_statusstringrequired

'none' | 'deposit_paid' | 'paid' | 'refunded' | 'partially_refunded' | 'failed'.

outstanding_pencenumberrequired

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.

collected_pencenumberrequired

What THIS call recorded. 0 on any outcome other than 'settled'.

settled_total_pencenumberrequired

Everything ever recorded as settled on this booking, this call included.

currencystringrequired
methodenumoptional

How the settle being reported was taken (null when nothing was ever owed).

cashcard_machine
settled_atstring, nullableoptional

ISO instant of the settle being reported.

settled_by_youbooleanrequired

true when the settle being reported is the caller's own.

settled_by_namestring, nullableoptional

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"
  }'
Response, 200
{
  "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 store's day, grouped by unit.

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.

Parameters

store_idstring · uuidqueryrequired
from_datestringqueryrequired

First day to show, in the STORE's timezone.

to_datestringqueryrequired

Last day to show (inclusive), in the STORE's timezone.

unit_idstring · uuid, nullablequeryoptional

Restrict to one unit. Omit for every unit (the full diary).

include_cancelledbooleanqueryoptional

Include cancelled appointments. Defaults to excluding them.

Response, 200

store_idstringrequired
timezonestringrequired

The store's IANA timezone, every instant below is anchored to it for display.

fromstringrequired

ISO instant the window starts (00:00 store-local on from_date).

tostringrequired

ISO instant the window ends, EXCLUSIVE (00:00 store-local the day after to_date).

unitsarray of objectrequired

Every active bookable unit, each with its appointments, holds and time-off.

4 child fields
unitobjectrequired
14 child fields
idstringrequired
display_namestringrequired
kindstringrequired
unit_typestring, nullablerequired
user_idstring, nullablerequired
colourstring, nullablerequired

Diary column colour.

photo_urlstring, nullablerequired
sortnumberrequired
bookablebooleanrequired
online_bookablebooleanrequired
is_activebooleanrequired
area_idstring · uuid, nullablerequired

The dining AREA this unit stands in, e.g. the Terrace. Null for every non-restaurant unit and for a table nobody has filed.

area_namestring, nullablerequired

Resolved name of area_id ("Dining room", "Terrace", "The bar"), joined from restaurant_areas. Lets the diary group a floor into its sections instead of one flat list of tables.

joins_witharray of stringrequired

The other tables this one pushes together with. Empty for a table that stands alone and for every non-restaurant unit.

appointmentsarray of objectrequired
36 child fields
idstringrequired
unit_idstringrequired

The PRIMARY table this booking is filed under. On a joined booking the same appointment appears in more than one column, and this is how the client knows which column is the original and which is the pushed-together one.

unit_idsarray of stringrequired

Every table this booking occupies. Two when tables were pushed together for a big party, one otherwise. The diary draws the booking on each of them, because a table that is genuinely taken must not look free to the person standing at the pass.

booking_refstring, nullablerequired

The short reference the diner quotes on the phone. Front of house needs to SEE it on the booking, not merely be able to search by it, or a caller reading a code back can never be told "yes, that is the one".

customer_idstring, nullablerequired
guest_namestring, nullablerequired
guest_emailstring, nullablerequired
guest_phonestring, nullablerequired
customer_namestring, nullablerequired

The account holder's name, for the ordinary signed-in booking that carries no guest_name. Without it the diary can only say "Customer". Null for a guest booking (read guest_name instead), and null for a customer who has been erased or archived, their booking survives, their name does not.

service_idstring, nullablerequired
starts_atstringrequired

ISO instant the appointment starts.

ends_atstringrequired

ISO instant the appointment ends (what the customer was told).

statusstringrequired

'pending_payment' | 'confirmed' | 'checked_in' | 'completed' | 'cancelled' | 'no_show'.

payment_statusstringrequired
price_pencenumberrequired
deposit_pencenumberrequired
balance_pencenumberrequired
currencystringrequired
party_sizenumberrequired
childrennumberrequired

How many of the party are children (age 12 and under). 0 for a normal appointment.

high_chairsnumberrequired

High chairs the diner asked for, so the host can fetch them.

preferred_area_idstring · uuid, nullablerequired

The dining AREA the guest asked for at booking (Level-1 seating). Null for no-preference and every non-restaurant booking.

preferred_area_namestring, nullablerequired

Resolved name of preferred_area_id (e.g. "Window"), joined from restaurant_areas. Null when there is no preference.

sourcestringrequired
is_overridebooleanrequired

true when this was FORCE-BOOKED (a squeeze-in exempt from the double-booking guard).

checked_in_atstring, nullablerequired
completed_atstring, nullablerequired
cancelled_atstring, nullablerequired
running_late_minutesnumber, nullablerequired

How many minutes late the diner says they will be. Null when nothing has been said. A LATER message replaces an earlier one, so this is always the current claim. The booking itself is UNCHANGED: running late shifts no time and re-lets no table.

running_late_atstring, nullablerequired

ISO instant the diner said it. Front of house needs the age of the claim as much as the number, "fifteen minutes late" said forty minutes ago is a different fact.

diner_notesarray of objectrequired

Everything the diner has told this restaurant about this booking, OLDEST FIRST. A list rather than one field because they accumulate: an allergy declared at noon must not be buried by a high chair asked for at six. Empty for every booking nobody has written to.

3 child fields
idstringrequired

Stable id of the ledger entry, so a client can diff without re-rendering.

atstringrequired

ISO instant it was sent.

textstringrequired

What they said, in their own words. At most 280 characters.

dietary_requirementsarray of enumrequired

The requirements standing on this booking. EMPTY IS NOT "NONE", read it together with `dietary_recorded_at`, which is the only field that says whether anybody has been asked.

CeleryGlutenCrustaceansEggsFishLupinMilkMolluscsMustardPeanutsSesameSoyaSulphitesTree nutsVegetarianVeganHalalKosherPescatarianDairy-freeGluten-free
dietary_notestring, nullablerequired

The detail, in the diner's own words, "severe, airborne". Never a replacement for the list above: anything the vocabulary can name names itself, and this carries what it cannot.

dietary_recorded_atstring, nullablerequired

🔴 ISO instant somebody last stated this booking's requirements. NULL means NOBODY HAS SAID, which is NOT the same fact as "no requirements" and must never be rendered as one. A client that drops this field cannot tell a coeliac table from a table nobody asked.

dietary_recorded_byenumrequired

Who stated it. Front of house treats "the diner told us" and "we typed it up off a phone call" differently, so the provenance travels with the fact.

customermerchantagent
dietary_has_allergenbooleanrequired

True when at least one requirement is one of the FOURTEEN a UK food business must declare, rather than a preference. Computed SERVER-SIDE from the one exported decider, deliberately: a client that held its own copy of the statutory list would be a second list to drift, and the distinction is not cosmetic, "vegan" is a choice a kitchen should honour, "peanuts" is a thing that can put somebody in an ambulance, and the two must never render as the same chip.

holdsarray of objectrequired

Live pending reservations mid-checkout.

4 child fields
idstringrequired
starts_atstringrequired
ends_atstringrequired
expires_atstring, nullablerequired

When this pending reservation lapses.

time_offarray of objectrequired

Time-off / lunch, from both the time-off entity and manual blocks.

5 child fields
idstringrequired
kindstringrequired

'time_off' (an explicit time-off entry) or 'block' (a manual lunch/errand block).

starts_atstringrequired
ends_atstringrequired
reasonstring, nullablerequired
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>"
Response, 200
{
  "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,
        "area_id": "d381ba0d-0000-4000-8000-d0c5000000d3",
        "area_name": "<area_name>",
        "joins_with": [
          "<joins_with>"
        ]
      },
      "appointments": [
        {
          "id": "00000d1b-0000-4000-8000-d0c500000000",
          "unit_id": "eeebf9b6-0000-4000-8000-d0c5000000ee",
          "unit_ids": [
            "eeebf9b6-0000-4000-8000-d0c5000000ee"
          ],
          "booking_ref": "<booking_ref>",
          "customer_id": "96607d1c-0000-4000-8000-d0c500000096",
          "guest_name": "<guest_name>",
          "guest_email": "alex@example.com",
          "guest_phone": "+44 7700 900123",
          "customer_name": "Alex Example",
          "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>",
          "running_late_minutes": 1,
          "running_late_at": "<running_late_at>",
          "diner_notes": [
            {
              "id": null,
              "at": null,
              "text": null
            }
          ],
          "dietary_requirements": [
            "Celery"
          ],
          "dietary_note": "Added at the front desk",
          "dietary_recorded_at": "<dietary_recorded_at>",
          "dietary_recorded_by": "customer",
          "dietary_has_allergen": true
        }
      ],
      "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 caller's own table requests.

Always scoped to the signed-in customer, and the scoping is part of the query rather than a filter applied afterwards. There is deliberately no customer_id parameter and no store-wide list here, the venue's view is a different, business-authenticated surface. This is the read that answers "did I ask, and has anybody replied", which is the question that otherwise makes people ring up.

Parameters

store_idstring · uuid, nullablequeryoptional

Narrow to one venue. Omit for every venue this person has asked.

include_closedbooleanqueryoptional

Include answered, declined, expired and withdrawn requests. Default false, the open ones are what somebody opens this list for.

limitintegerqueryoptional

Response, 200

enquiriesarray of objectrequired

Newest first.

25 child fields
idstringrequired
enquiry_refstring, nullablerequired

The short code the diner quotes while this is still a request, six characters from the same alphabet a booking reference uses, and unique within the store across BOTH requests and bookings, so a host handed a code never has to ask which kind of thing it is. When the request is accepted the resulting booking gets its own booking reference, and THAT is the one the diner keeps.

store_idstringrequired
statusenumrequired

'pending' asked, nobody has answered · 'accepted' the venue turned it into a real booking · 'declined' the venue said no, and `decline_reason` says why · 'expired' the day went by without an answer, which is the commonest outcome and a DESIGNED one, not a failure · 'withdrawn' the diner took it back.

pendingaccepteddeclinedexpiredwithdrawn
party_sizenumberrequired
childrennumberrequired

Of the party, how many are children. Counted within party_size, never on top of it.

high_chairsnumberrequired
requested_datestringrequired

The day they want, in the STORE's timezone, YYYY-MM-DD.

requested_timestring, nullablerequired

The time they want, local to the store, 24 hour. Omit for 'any time that day'.

flexible_timebooleanrequired

They would take another time that day.

flexible_datebooleanrequired

They would take another day.

service_idstring, nullablerequired
preferred_area_idstring, nullablerequired

The area they asked for, carried forward from the booking flow. A preference, never a promise.

notestring, nullablerequired

Anything the flow did not already know, in their own words.

guest_namestring, nullablerequired
guest_emailstring, nullablerequired
guest_phonestring, nullablerequired
decline_reasonstring, nullablerequired

Why the venue said no, in their words. Present only on a declined request, and it reaches the diner.

appointment_idstring, nullablerequired

The booking this became. Present only when accepted.

booking_refstring, nullablerequired

The reference of the booking this became. Present only when accepted.

expires_atstringrequired

ISO instant this request stops being answerable. The end of the requested day in the store's zone, or seven days past it when they said they were flexible on the date. Stated to the diner at the moment they ask, because a request with no stated end is a promise nobody made.

nudged_atstring, nullablerequired

When the venue was reminded. One reminder, ever, a second is nagging, and a nagged merchant mutes the channel.

responded_atstring, nullablerequired
created_atstringrequired
seating_optionsarray of objectoptional

Where this party could ACTUALLY sit: the runs of tables that would hold them, built from the merchant's own declared joins, the same column the customer availability search uses, so the two can never drift apart. Fewest tables first, then tightest fit, because burning two tables where one would do is how a floor plan quietly loses a sitting. EMPTY is a real answer and an important one: it means nothing on this floor holds them, and whatever the host picks she will be moving chairs. Merchant reads only.

5 child fields
unit_idsarray of stringrequired

The tables in this run. One id means a single table.

namesarray of stringrequired

Their names, as the merchant wrote them, 'Decking 2', 'Decking 3'.

seats_totalnumberrequired

What they seat between them.

table_countnumberrequired
is_joinbooleanrequired

True when this is more than one table pushed together.

curl -G "https://www.membber.com/api/v1/bookings/enquiries" \
  -H "Authorization: Bearer $MEMBBER_TOKEN"
Response, 200
{
  "enquiries": [
    {
      "id": "00000d1b-0000-4000-8000-d0c500000000",
      "enquiry_ref": "<enquiry_ref>",
      "store_id": "6659c139-0000-4000-8000-d0c500000066",
      "status": "pending",
      "party_size": 1,
      "children": 1,
      "high_chairs": 1,
      "requested_date": "<requested_date>",
      "requested_time": "<requested_time>",
      "flexible_time": true,
      "flexible_date": true,
      "service_id": "993232e5-0000-4000-8000-d0c500000099",
      "preferred_area_id": "afa05c4f-0000-4000-8000-d0c5000000af",
      "note": "Added at the front desk",
      "guest_name": "<guest_name>",
      "guest_email": "alex@example.com",
      "guest_phone": "+44 7700 900123",
      "decline_reason": "Added at the front desk",
      "appointment_id": "0d727dbb-0000-4000-8000-d0c50000000d",
      "booking_ref": "<booking_ref>",
      "expires_at": "<expires_at>",
      "nudged_at": "<nudged_at>",
      "responded_at": "<responded_at>",
      "created_at": "<created_at>",
      "seating_options": [
        {
          "unit_ids": [
            "eeebf9b6-0000-4000-8000-d0c5000000ee"
          ],
          "names": [
            "Example name"
          ],
          "seats_total": 1,
          "table_count": 1,
          "is_join": true
        }
      ]
    }
  ]
}

Ask a venue for a table that availability could not offer.

For the party that does not fit, the night that is full, and the booking made inside the venue's notice period. Reserves nothing and charges nothing: it puts a question in front of the venue, who may turn it into a real booking or decline it with a reason. Everything the caller already knows is carried in, so the diner is never asked twice. Named error codes: NOT_REACHABLE (no customer and no email, nowhere to send the answer), PAST_DATE (the day has gone), ENQUIRY_LIMIT (three open requests at this venue already), BOOKINGS_UNAVAILABLE (this venue does not take bookings at all).

auth none, publicop createBookingEnquiryidempotent retryrate limitederrors

Request body

store_idstring · uuidrequired

The venue being asked.

party_sizeintegerrequired

How many people. This is the number that did not fit.

requested_datestringrequired

The day they want, in the STORE's timezone, YYYY-MM-DD.

requested_timestring, nullableoptional

The time they want, local to the store, 24 hour. Omit for 'any time that day'.

flexible_timebooleanoptional

True if they would take another time that day. Default false, never assume flexibility nobody offered.

flexible_datebooleanoptional

True if they would take another day. Default false.

childrenintegeroptional
high_chairsintegeroptional
service_idstring · uuid, nullableoptional

Carried forward from the flow they were already in. A stale or foreign one is dropped to null rather than refusing the request.

preferred_area_idstring · uuid, nullableoptional

Carried forward. Same rule: dropped, never fatal.

notestring, nullableoptional

The only free text. Bounded, because it is rendered into an email.

guest_namestring, nullableoptional
guest_emailstring · email, nullableoptional

Required when there is no signed-in customer. The answer has to have somewhere to go: a request nobody can reply to is worse than no request, because the diner waits.

guest_phonestring, nullableoptional

Optional, and worth asking for: a restaurant answering at 18:50 for a 19:00 table will ring, not email.

client_tokenstring, nullableoptional

Caller-generated idempotency key. The SAME token returns the SAME request, so a double tap or a retried POST never leaves two parties waiting on one table.

Response, 200

enquiryobjectrequired
25 child fields
idstringrequired
enquiry_refstring, nullablerequired

The short code the diner quotes while this is still a request, six characters from the same alphabet a booking reference uses, and unique within the store across BOTH requests and bookings, so a host handed a code never has to ask which kind of thing it is. When the request is accepted the resulting booking gets its own booking reference, and THAT is the one the diner keeps.

store_idstringrequired
statusenumrequired

'pending' asked, nobody has answered · 'accepted' the venue turned it into a real booking · 'declined' the venue said no, and `decline_reason` says why · 'expired' the day went by without an answer, which is the commonest outcome and a DESIGNED one, not a failure · 'withdrawn' the diner took it back.

pendingaccepteddeclinedexpiredwithdrawn
party_sizenumberrequired
childrennumberrequired

Of the party, how many are children. Counted within party_size, never on top of it.

high_chairsnumberrequired
requested_datestringrequired

The day they want, in the STORE's timezone, YYYY-MM-DD.

requested_timestring, nullablerequired

The time they want, local to the store, 24 hour. Omit for 'any time that day'.

flexible_timebooleanrequired

They would take another time that day.

flexible_datebooleanrequired

They would take another day.

service_idstring, nullablerequired
preferred_area_idstring, nullablerequired

The area they asked for, carried forward from the booking flow. A preference, never a promise.

notestring, nullablerequired

Anything the flow did not already know, in their own words.

guest_namestring, nullablerequired
guest_emailstring, nullablerequired
guest_phonestring, nullablerequired
decline_reasonstring, nullablerequired

Why the venue said no, in their words. Present only on a declined request, and it reaches the diner.

appointment_idstring, nullablerequired

The booking this became. Present only when accepted.

booking_refstring, nullablerequired

The reference of the booking this became. Present only when accepted.

expires_atstringrequired

ISO instant this request stops being answerable. The end of the requested day in the store's zone, or seven days past it when they said they were flexible on the date. Stated to the diner at the moment they ask, because a request with no stated end is a promise nobody made.

nudged_atstring, nullablerequired

When the venue was reminded. One reminder, ever, a second is nagging, and a nagged merchant mutes the channel.

responded_atstring, nullablerequired
created_atstringrequired
seating_optionsarray of objectoptional

Where this party could ACTUALLY sit: the runs of tables that would hold them, built from the merchant's own declared joins, the same column the customer availability search uses, so the two can never drift apart. Fewest tables first, then tightest fit, because burning two tables where one would do is how a floor plan quietly loses a sitting. EMPTY is a real answer and an important one: it means nothing on this floor holds them, and whatever the host picks she will be moving chairs. Merchant reads only.

5 child fields
unit_idsarray of stringrequired

The tables in this run. One id means a single table.

namesarray of stringrequired

Their names, as the merchant wrote them, 'Decking 2', 'Decking 3'.

seats_totalnumberrequired

What they seat between them.

table_countnumberrequired
is_joinbooleanrequired

True when this is more than one table pushed together.

reusedbooleanrequired

True when this token already had a request and it came back unchanged (idempotent replay).

curl -X POST "https://www.membber.com/api/v1/bookings/enquiries" \
  -H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
  -H "Content-Type: application/json" \
  -d '{
    "store_id": "6659c139-0000-4000-8000-d0c500000066",
    "party_size": 1,
    "requested_date": "<requested_date>"
  }'
Response, 200
{
  "enquiry": {
    "id": "00000d1b-0000-4000-8000-d0c500000000",
    "enquiry_ref": "<enquiry_ref>",
    "store_id": "6659c139-0000-4000-8000-d0c500000066",
    "status": "pending",
    "party_size": 1,
    "children": 1,
    "high_chairs": 1,
    "requested_date": "<requested_date>",
    "requested_time": "<requested_time>",
    "flexible_time": true,
    "flexible_date": true,
    "service_id": "993232e5-0000-4000-8000-d0c500000099",
    "preferred_area_id": "afa05c4f-0000-4000-8000-d0c5000000af",
    "note": "Added at the front desk",
    "guest_name": "<guest_name>",
    "guest_email": "alex@example.com",
    "guest_phone": "+44 7700 900123",
    "decline_reason": "Added at the front desk",
    "appointment_id": "0d727dbb-0000-4000-8000-d0c50000000d",
    "booking_ref": "<booking_ref>",
    "expires_at": "<expires_at>",
    "nudged_at": "<nudged_at>",
    "responded_at": "<responded_at>",
    "created_at": "<created_at>",
    "seating_options": [
      {
        "unit_ids": [
          "eeebf9b6-0000-4000-8000-d0c5000000ee"
        ],
        "names": [
          "Example name"
        ],
        "seats_total": 1,
        "table_count": 1,
        "is_join": true
      }
    ]
  },
  "reused": true
}

The requests waiting on this venue.

Business-auth and scoped to the AUTHENTICATED store, never a client-supplied one. Returns the whole request, party, children, high chairs, the area they hoped for, their note, and crucially whether they are flexible, because the answer "not 19:00 but I can do 20:30" is only possible if the host can see that they said they could bend.

auth merchant:readop getBookingEnquiryInboxrate limitederrors

Parameters

statusenumqueryoptional

Default 'pending', the list somebody actually opens. 'all' includes answered, declined and expired requests for the window.

pendingall
from_datestring, nullablequeryoptional

Narrow by the REQUESTED date, not by when it was asked. Omit for from today.

to_datestring, nullablequeryoptional

The day they want, in the STORE's timezone, YYYY-MM-DD.

refstring, nullablequeryoptional

Look one up by the code a caller is reading down the phone. Case-insensitive.

limitintegerqueryoptional

Response, 200

enquiriesarray of objectrequired

Soonest requested date first, then oldest asked first, the order a host would work them in.

30 child fields
idstringrequired
enquiry_refstring, nullablerequired

The short code the diner quotes while this is still a request, six characters from the same alphabet a booking reference uses, and unique within the store across BOTH requests and bookings, so a host handed a code never has to ask which kind of thing it is. When the request is accepted the resulting booking gets its own booking reference, and THAT is the one the diner keeps.

store_idstringrequired
statusenumrequired

'pending' asked, nobody has answered · 'accepted' the venue turned it into a real booking · 'declined' the venue said no, and `decline_reason` says why · 'expired' the day went by without an answer, which is the commonest outcome and a DESIGNED one, not a failure · 'withdrawn' the diner took it back.

pendingaccepteddeclinedexpiredwithdrawn
party_sizenumberrequired
childrennumberrequired

Of the party, how many are children. Counted within party_size, never on top of it.

high_chairsnumberrequired
requested_datestringrequired

The day they want, in the STORE's timezone, YYYY-MM-DD.

requested_timestring, nullablerequired

The time they want, local to the store, 24 hour. Omit for 'any time that day'.

flexible_timebooleanrequired

They would take another time that day.

flexible_datebooleanrequired

They would take another day.

service_idstring, nullablerequired
preferred_area_idstring, nullablerequired

The area they asked for, carried forward from the booking flow. A preference, never a promise.

notestring, nullablerequired

Anything the flow did not already know, in their own words.

guest_namestring, nullablerequired
guest_emailstring, nullablerequired
guest_phonestring, nullablerequired
decline_reasonstring, nullablerequired

Why the venue said no, in their words. Present only on a declined request, and it reaches the diner.

appointment_idstring, nullablerequired

The booking this became. Present only when accepted.

booking_refstring, nullablerequired

The reference of the booking this became. Present only when accepted.

expires_atstringrequired

ISO instant this request stops being answerable. The end of the requested day in the store's zone, or seven days past it when they said they were flexible on the date. Stated to the diner at the moment they ask, because a request with no stated end is a promise nobody made.

nudged_atstring, nullablerequired

When the venue was reminded. One reminder, ever, a second is nagging, and a nagged merchant mutes the channel.

responded_atstring, nullablerequired
created_atstringrequired
seating_optionsarray of objectoptional

Where this party could ACTUALLY sit: the runs of tables that would hold them, built from the merchant's own declared joins, the same column the customer availability search uses, so the two can never drift apart. Fewest tables first, then tightest fit, because burning two tables where one would do is how a floor plan quietly loses a sitting. EMPTY is a real answer and an important one: it means nothing on this floor holds them, and whatever the host picks she will be moving chairs. Merchant reads only.

5 child fields
unit_idsarray of stringrequired

The tables in this run. One id means a single table.

namesarray of stringrequired

Their names, as the merchant wrote them, 'Decking 2', 'Decking 3'.

seats_totalnumberrequired

What they seat between them.

table_countnumberrequired
is_joinbooleanrequired

True when this is more than one table pushed together.

customer_idstring, nullablerequired
customer_namestring, nullablerequired

The account holder's name, for a signed-in diner who gave no guest name. Null for a guest, and null for a customer who has been erased.

preferred_area_namestring, nullablerequired

Resolved name of the area they asked for, e.g. "Terrace".

service_namestring, nullablerequired
sourcestringrequired

'organic' a diner in the app · 'agent' a phone or WhatsApp agent asking on their behalf · 'merchant' the venue logging a request that arrived another way.

pending_countnumberrequired

How many are still waiting on an answer, across every date. The badge number.

curl -G "https://www.membber.com/api/v1/bookings/enquiries/inbox" \
  -H "Authorization: Bearer $MEMBBER_TOKEN"
Response, 200
{
  "enquiries": [
    {
      "id": "00000d1b-0000-4000-8000-d0c500000000",
      "enquiry_ref": "<enquiry_ref>",
      "store_id": "6659c139-0000-4000-8000-d0c500000066",
      "status": "pending",
      "party_size": 1,
      "children": 1,
      "high_chairs": 1,
      "requested_date": "<requested_date>",
      "requested_time": "<requested_time>",
      "flexible_time": true,
      "flexible_date": true,
      "service_id": "993232e5-0000-4000-8000-d0c500000099",
      "preferred_area_id": "afa05c4f-0000-4000-8000-d0c5000000af",
      "note": "Added at the front desk",
      "guest_name": "<guest_name>",
      "guest_email": "alex@example.com",
      "guest_phone": "+44 7700 900123",
      "decline_reason": "Added at the front desk",
      "appointment_id": "0d727dbb-0000-4000-8000-d0c50000000d",
      "booking_ref": "<booking_ref>",
      "expires_at": "<expires_at>",
      "nudged_at": "<nudged_at>",
      "responded_at": "<responded_at>",
      "created_at": "<created_at>",
      "seating_options": [
        {
          "unit_ids": [
            "eeebf9b6-0000-4000-8000-d0c5000000ee"
          ],
          "names": [
            "Example name"
          ],
          "seats_total": 1,
          "table_count": 1,
          "is_join": true
        }
      ],
      "customer_id": "96607d1c-0000-4000-8000-d0c500000096",
      "customer_name": "Alex Example",
      "preferred_area_name": "<preferred_area_name>",
      "service_name": "<service_name>",
      "source": "<source>"
    }
  ],
  "pending_count": 1
}

Accept a request into a real booking, or decline it with a reason.

ONE ACTION. Accept creates the appointment and closes the request against it inside a single transaction, so there is never a diner holding a booking nobody can find, nor a request marked answered with nothing behind it. It FORCE-BOOKS deliberately: a party of eight at a six-seat venue fits no table by construction, which is the entire reason the request exists, so an accept that respected the double-booking guard could never succeed for the case it was built for. The host is standing in the room and has decided. Both outcomes reach the diner. A party too big for one table is seated across a RUN of tables the merchant declared joinable (`unit_ids`), each genuinely blocked, so the second table cannot also be sold. Where they still do not fit, the answer says so, `seats_total` and `over_capacity` come home on every accept, rather than confirming in silence. Named error codes: TIME_REQUIRED (accept with no time), UNIT_NOT_FOUND, BAD_WINDOW, TOO_MANY_TABLES (more than six), PARTY_TOO_LARGE (they do not fit and the caller did not say `accept_over_capacity`), JOIN_NOT_ALLOWED (those tables are not declared as pushing together), STALE_SLOT (that table went while you were deciding, pick another).

Request body

enquiry_idstring · uuidrequired
actionenumrequired
acceptdecline
unit_idstring · uuid, nullableoptional

Accept only: the table the party is going on. Kept for callers that can only name ONE table (an installed binary that predates `unit_ids`); prefer `unit_ids`. Ignored when `unit_ids` is sent.

unit_idsarray of string · uuidoptional

Accept only, and the one to use: EVERY table the party is going on. A party too big for any single table is seated across a run of tables the merchant declared joinable, each one is genuinely blocked, so the second table cannot also be sold to somebody else. The first id is the primary (the booking hangs off it); the rest ride as a join. `enquiry_seating_options` on each request already lists the runs that would hold this party, so a caller does not have to work out the combinations itself. One id behaves exactly like `unit_id`.

accept_over_capacitybooleanoptional

Accept only. Say true to mean "yes, this party is bigger than these tables seat, and I am putting them there anyway", the normal, allowed thing a host does when she is moving chairs. It is not required today, because the host is standing in the room and the app does not argue with her. It exists because a caller that is NOT in the room (a phone or WhatsApp agent) should have to say it out loud rather than promise a room something it cannot do. When it is required and missing, the answer is PARTY_TOO_LARGE with both numbers in it.

starts_atstring, nullableoptional

Accept only: ISO instant the table is for. This is the HOST's time, not the diner's, the whole value of "I am flexible" is that the answer may be a different time, or a different day, from the one asked for. Required to accept.

ends_atstring, nullableoptional

Accept only: ISO instant the sitting ends. Required to accept.

decline_reasonstring, nullableoptional

Decline only, and it REACHES THE DINER. A silent no is what makes people ring up, and a reason in the venue's own words is often an offer in disguise ("not Friday, but Saturday is wide open").

Response, 200

enquiry_idstringrequired
statusenumrequired

'pending' asked, nobody has answered · 'accepted' the venue turned it into a real booking · 'declined' the venue said no, and `decline_reason` says why · 'expired' the day went by without an answer, which is the commonest outcome and a DESIGNED one, not a failure · 'withdrawn' the diner took it back.

pendingaccepteddeclinedexpiredwithdrawn
appointment_idstring, nullablerequired

The booking that now exists. Present on accept.

booking_refstring, nullablerequired

Its reference, the one the diner keeps and quotes at the door.

noopbooleanrequired

True when this request had already been answered and nothing changed. A second tap is never a second booking.

unit_idsarray of stringoptional

Every table the booking occupies. One id for a single table, more for a run pushed together.

party_sizenumber, nullableoptional

How many people the request was for.

seats_totalnumber, nullableoptional

What those tables seat between them, from the merchant's own declared sizes. Null where the merchant has never said what a table seats, an undeclared table is not "too small", nobody has said.

over_capacitybooleanoptional

True when the party is bigger than the tables seat. NOT an error, it is the allowed, deliberate thing a host does when she is moving chairs, and it is recorded rather than refused. It is here so no caller has to GUESS whether what it just confirmed actually fits.

curl -X POST "https://www.membber.com/api/v1/bookings/enquiries/respond" \
  -H "Authorization: Bearer $MEMBBER_TOKEN" \
  -H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
  -H "Content-Type: application/json" \
  -d '{
    "enquiry_id": "d7aafa17-0000-4000-8000-d0c5000000d7",
    "action": "accept"
  }'
Response, 200
{
  "enquiry_id": "d7aafa17-0000-4000-8000-d0c5000000d7",
  "status": "pending",
  "appointment_id": "0d727dbb-0000-4000-8000-d0c50000000d",
  "booking_ref": "<booking_ref>",
  "noop": true,
  "unit_ids": [
    "eeebf9b6-0000-4000-8000-d0c5000000ee"
  ],
  "party_size": 1,
  "seats_total": 1,
  "over_capacity": true
}

Take back a table request.

The diner sorted their evening elsewhere. Withdrawing frees the venue from answering and frees the diner from the open-request cap. Idempotent: withdrawing an already-closed request returns it unchanged rather than erroring, and a request the venue has already ACCEPTED cannot be withdrawn this way, that is a real booking now, so it is cancelled, not un-asked.

Request body

enquiry_idstring · uuidrequired

The request to take back.

Response, 200

enquiryobjectrequired
25 child fields
idstringrequired
enquiry_refstring, nullablerequired

The short code the diner quotes while this is still a request, six characters from the same alphabet a booking reference uses, and unique within the store across BOTH requests and bookings, so a host handed a code never has to ask which kind of thing it is. When the request is accepted the resulting booking gets its own booking reference, and THAT is the one the diner keeps.

store_idstringrequired
statusenumrequired

'pending' asked, nobody has answered · 'accepted' the venue turned it into a real booking · 'declined' the venue said no, and `decline_reason` says why · 'expired' the day went by without an answer, which is the commonest outcome and a DESIGNED one, not a failure · 'withdrawn' the diner took it back.

pendingaccepteddeclinedexpiredwithdrawn
party_sizenumberrequired
childrennumberrequired

Of the party, how many are children. Counted within party_size, never on top of it.

high_chairsnumberrequired
requested_datestringrequired

The day they want, in the STORE's timezone, YYYY-MM-DD.

requested_timestring, nullablerequired

The time they want, local to the store, 24 hour. Omit for 'any time that day'.

flexible_timebooleanrequired

They would take another time that day.

flexible_datebooleanrequired

They would take another day.

service_idstring, nullablerequired
preferred_area_idstring, nullablerequired

The area they asked for, carried forward from the booking flow. A preference, never a promise.

notestring, nullablerequired

Anything the flow did not already know, in their own words.

guest_namestring, nullablerequired
guest_emailstring, nullablerequired
guest_phonestring, nullablerequired
decline_reasonstring, nullablerequired

Why the venue said no, in their words. Present only on a declined request, and it reaches the diner.

appointment_idstring, nullablerequired

The booking this became. Present only when accepted.

booking_refstring, nullablerequired

The reference of the booking this became. Present only when accepted.

expires_atstringrequired

ISO instant this request stops being answerable. The end of the requested day in the store's zone, or seven days past it when they said they were flexible on the date. Stated to the diner at the moment they ask, because a request with no stated end is a promise nobody made.

nudged_atstring, nullablerequired

When the venue was reminded. One reminder, ever, a second is nagging, and a nagged merchant mutes the channel.

responded_atstring, nullablerequired
created_atstringrequired
seating_optionsarray of objectoptional

Where this party could ACTUALLY sit: the runs of tables that would hold them, built from the merchant's own declared joins, the same column the customer availability search uses, so the two can never drift apart. Fewest tables first, then tightest fit, because burning two tables where one would do is how a floor plan quietly loses a sitting. EMPTY is a real answer and an important one: it means nothing on this floor holds them, and whatever the host picks she will be moving chairs. Merchant reads only.

5 child fields
unit_idsarray of stringrequired

The tables in this run. One id means a single table.

namesarray of stringrequired

Their names, as the merchant wrote them, 'Decking 2', 'Decking 3'.

seats_totalnumberrequired

What they seat between them.

table_countnumberrequired
is_joinbooleanrequired

True when this is more than one table pushed together.

curl -X POST "https://www.membber.com/api/v1/bookings/enquiries/withdraw" \
  -H "Authorization: Bearer $MEMBBER_TOKEN" \
  -H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
  -H "Content-Type: application/json" \
  -d '{
    "enquiry_id": "d7aafa17-0000-4000-8000-d0c5000000d7"
  }'
Response, 200
{
  "enquiry": {
    "id": "00000d1b-0000-4000-8000-d0c500000000",
    "enquiry_ref": "<enquiry_ref>",
    "store_id": "6659c139-0000-4000-8000-d0c500000066",
    "status": "pending",
    "party_size": 1,
    "children": 1,
    "high_chairs": 1,
    "requested_date": "<requested_date>",
    "requested_time": "<requested_time>",
    "flexible_time": true,
    "flexible_date": true,
    "service_id": "993232e5-0000-4000-8000-d0c500000099",
    "preferred_area_id": "afa05c4f-0000-4000-8000-d0c5000000af",
    "note": "Added at the front desk",
    "guest_name": "<guest_name>",
    "guest_email": "alex@example.com",
    "guest_phone": "+44 7700 900123",
    "decline_reason": "Added at the front desk",
    "appointment_id": "0d727dbb-0000-4000-8000-d0c50000000d",
    "booking_ref": "<booking_ref>",
    "expires_at": "<expires_at>",
    "nudged_at": "<nudged_at>",
    "responded_at": "<responded_at>",
    "created_at": "<created_at>",
    "seating_options": [
      {
        "unit_ids": [
          "eeebf9b6-0000-4000-8000-d0c5000000ee"
        ],
        "names": [
          "Example name"
        ],
        "seats_total": 1,
        "table_count": 1,
        "is_join": true
      }
    ]
  }
}

The store's live walk-in queue.

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.

Parameters

store_idstring · uuidqueryrequired

The store whose queue to read. Must be the authenticated store.

Response, 200

store_idstringrequired
walkin_modestring, nullablerequired

'appointments_only' | 'walk_ins_only' | 'mixed', whether the store even takes walk-ins.

called_hold_minutesnumber, nullablerequired

The hold-after-call window, for the rail's countdown on called entries.

entriesarray of objectrequired

Every waiting + called entry in join order. Waiting entries carry a derived position + wait + suggested chair; called entries carry called_at.

17 child fields
entry_idstringrequired
store_idstringrequired
customer_idstring, nullablerequired
guest_namestring, nullablerequired
guest_phonestring, nullablerequired

Present on the merchant surface; omitted from the customer surface.

service_idstring, nullablerequired

The service the walk-in asked for, shapes the wait estimate and the assigned duration.

preferred_unit_idstring, nullablerequired

A chair/stylist the walk-in asked for, if any.

party_sizenumberrequired
statusstringrequired

'waiting' | 'called' | 'assigned' | 'completed' | 'left' | 'no_show' | 'converted_to_appointment'.

joined_atstringrequired

ISO instant they joined the queue (the join order that derives position).

called_atstring, nullablerequired

ISO instant they were called, if they have been.

quoted_wait_minnumber, nullablerequired

The wait we quoted AT JOIN, snapshotted, so a later dispute is answerable.

appointment_idstring, nullablerequired

Set once assigned, the booking the entry converted into.

queue_positionnumber, nullablerequired

DERIVED 1-based place in line. Null once called/assigned/left, a stored position is a second source of truth that drifts.

expected_wait_minnumber, nullablerequired

DERIVED minutes until a chair is expected free for this person, from the live blocks. Null when not waiting.

expected_startstring, nullablerequired

DERIVED ISO instant a chair is expected free for this person. Null when not waiting.

suggested_unit_idstring, nullablerequired

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"
Response, 200
{
  "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"
    }
  ]
}

Seat a walk-in at a unit (books it).

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).

Request body

store_idstring · uuidrequired

The authenticated store the entry + unit belong to.

entry_idstring · uuidrequired

The walk-in to seat. Must belong to the store.

unit_idstring · uuidrequired

The chair/stylist/table to seat them at, starting now(). Must belong to the store.

duration_mininteger, nullableoptional

Override the assigned length. Defaults to the service duration (or 30 min).

overridebooleanoptional

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.

Response, 200

entry_idstringrequired
appointment_idstringrequired

The booking the walk-in became, created through the ONE booking authority, source "walk_in".

assignedbooleanrequired

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"
  }'
Response, 200
{
  "entry_id": "83353c08-0000-4000-8000-d0c500000083",
  "appointment_id": "0d727dbb-0000-4000-8000-d0c50000000d",
  "assigned": true
}

Call the next (or a specific) walk-in.

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.

Request body

store_idstring · uuidrequired

The authenticated store the entry belongs to.

entry_idstring · uuidrequired

The entry to call. "Call the next" is the first waiting entry from the list. Must belong to the store.

Response, 200

entry_idstringrequired
calledbooleanrequired

false when it was already called, an idempotent no-op.

hold_minutesnumberrequired

How long they are held before they lose their place.

called_atstring, nullablerequired

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"
  }'
Response, 200
{
  "entry_id": "83353c08-0000-4000-8000-d0c500000083",
  "called": true,
  "hold_minutes": 1,
  "called_at": "<called_at>"
}

Remove a walk-in (left / no-show).

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).

Request body

store_idstring · uuidrequired

The authenticated store the entry belongs to.

entry_idstring · uuidrequired

The entry to remove. Must belong to the store.

statusenumrequired

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_show

Response, 200

entry_idstringrequired
statusstringrequired

The terminal status the entry now holds.

noopbooleanrequired

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"
  }'
Response, 200
{
  "entry_id": "83353c08-0000-4000-8000-d0c500000083",
  "status": "<status>",
  "noop": true
}

A store's service periods.

Every service period (Lunch/Dinner/…) for the authenticated store. Disabled periods are excluded unless asked for.

Parameters

store_idstring · uuidqueryrequired
include_archivedbooleanqueryoptional

Include disabled periods. Defaults to active only.

Response, 200

periodsarray of objectrequired
8 child fields
idstringrequired
store_idstringrequired
namestringrequired
sortnumberrequired
weekobjectrequired

The week this period is served. Omit a day to close it. e.g. {"friday":{"open":"18:00","close":"22:00"}}.

7 child fields
sundayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

mondayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

tuesdayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

wednesdayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

thursdayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

fridayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

saturdayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

last_seating_offset_minnumberrequired
is_activebooleanrequired
categoriesarray of stringrequired

The merchant's own menu sections served on this period, e.g. ["Breakfast","Breakfast Extras"]. Empty means no diner ever sees this period.

store_sectionsarray of stringrequired

Every menu section this store actually has (distinct `menu_items.category`, live items only), alphabetical. These are the only values `PUT .../sections` accepts.

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"
Response, 200
{
  "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,
      "categories": [
        "<categorie>"
      ]
    }
  ],
  "store_sections": [
    "<store_section>"
  ]
}

Create a service period.

Adds a named service period to the authenticated store. Send a `client_token` to make the create idempotent.

Request body

store_idstring · uuidrequired
client_tokenstringoptional

Caller-generated idempotency key, a retry with the same token returns the SAME period, never a duplicate.

namestringrequired

What the period is called, e.g. "Lunch" or "Dinner".

sortintegeroptional

Display order among the store's periods.

weekobjectoptional

The week this period is served. Omit a day to close it. e.g. {"friday":{"open":"18:00","close":"22:00"}}.

7 child fields
sundayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

mondayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

tuesdayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

wednesdayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

thursdayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

fridayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

saturdayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

last_seating_offset_minintegeroptional

Minutes before this period closes that the last table is seated. Overrides the store policy value for this period.

Response, 200

periodobjectrequired
8 child fields
idstringrequired
store_idstringrequired
namestringrequired
sortnumberrequired
weekobjectrequired

The week this period is served. Omit a day to close it. e.g. {"friday":{"open":"18:00","close":"22:00"}}.

7 child fields
sundayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

mondayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

tuesdayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

wednesdayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

thursdayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

fridayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

saturdayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

last_seating_offset_minnumberrequired
is_activebooleanrequired
categoriesarray of stringrequired

The merchant's own menu sections served on this period, e.g. ["Breakfast","Breakfast Extras"]. Empty means no diner ever sees this period.

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"
  }'
Response, 200
{
  "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,
    "categories": [
      "<categorie>"
    ]
  }
}

Delete a service period.

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.

Parameters

periodIdstringpathrequired
store_idstring · uuidqueryrequired

Response, 200

period_idstringrequired
removedbooleanrequired

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"
Response, 200
{
  "period_id": "21c59859-0000-4000-8000-d0c500000021",
  "removed": true
}

Update a service period.

Partial update of a service period the authenticated store owns. A window change shapes FUTURE availability only.

Parameters

periodIdstringpathrequired

Request body

store_idstring · uuidrequired
namestringoptional

What the period is called, e.g. "Lunch" or "Dinner".

sortintegeroptional

Display order among the store's periods.

weekobjectoptional

The week this period is served. Omit a day to close it. e.g. {"friday":{"open":"18:00","close":"22:00"}}.

7 child fields
sundayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

mondayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

tuesdayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

wednesdayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

thursdayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

fridayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

saturdayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

last_seating_offset_minintegeroptional

Minutes before this period closes that the last table is seated. Overrides the store policy value for this period.

is_activebooleanoptional

Set false to disable this period without deleting it; true to re-enable.

Response, 200

periodobjectrequired
8 child fields
idstringrequired
store_idstringrequired
namestringrequired
sortnumberrequired
weekobjectrequired

The week this period is served. Omit a day to close it. e.g. {"friday":{"open":"18:00","close":"22:00"}}.

7 child fields
sundayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

mondayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

tuesdayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

wednesdayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

thursdayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

fridayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

saturdayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

last_seating_offset_minnumberrequired
is_activebooleanrequired
categoriesarray of stringrequired

The merchant's own menu sections served on this period, e.g. ["Breakfast","Breakfast Extras"]. Empty means no diner ever sees this period.

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"
  }'
Response, 200
{
  "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,
    "categories": [
      "<categorie>"
    ]
  }
}

Set which menu sections are served on a service period.

Replaces the whole set of menu sections on one card in a single write, so the merchant can say what Breakfast actually is. Section names are the merchant's own `menu_items.category` values, matched case- and whitespace-insensitively; an unknown section is a 422 naming it. Idempotent (the same payload converges). Returns the period, so the client adopts the server's answer rather than what it sent.

Parameters

periodIdstringpathrequired

Request body

store_idstring · uuidrequired

Store the period belongs to (also authorises the merchant).

sectionsarray of stringrequired

Every menu section served on this card, replacing what is there now. Must be sections the store actually has (see `store_sections` on the list response), an unknown one is refused by name rather than written as a link that serves nobody. An EMPTY array clears the card, which hides it from every diner; that is allowed and the merchant surface says so. A section may sit on more than one card (a café whose Hot Drinks runs all day), so this never touches other cards.

Response, 200

periodobjectrequired
8 child fields
idstringrequired
store_idstringrequired
namestringrequired
sortnumberrequired
weekobjectrequired

The week this period is served. Omit a day to close it. e.g. {"friday":{"open":"18:00","close":"22:00"}}.

7 child fields
sundayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

mondayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

tuesdayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

wednesdayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

thursdayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

fridayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

saturdayobjectoptional

One serving window for one day. May cross midnight.

2 child fields
openstringrequired

When this period opens on this day (24h HH:MM).

closestringrequired

When this period closes on this day (24h HH:MM). A close EARLIER than the open means the service runs past midnight: "open":"21:00","close":"01:00" is a late bar serving until 1am the following morning. Only open == close is refused, because a zero-length window serves nobody.

last_seating_offset_minnumberrequired
is_activebooleanrequired
categoriesarray of stringrequired

The merchant's own menu sections served on this period, e.g. ["Breakfast","Breakfast Extras"]. Empty means no diner ever sees this period.

curl -X PUT "https://www.membber.com/api/v1/bookings/service-periods/19dd12bc-0000-4000-8000-d0c500000019/sections" \
  -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",
    "sections": [
      "<section>"
    ]
  }'
Response, 200
{
  "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,
    "categories": [
      "<categorie>"
    ]
  }
}

A store's bookable services.

Every service the store offers, each with its variants and add-ons. Scoped to the authenticated store; archived services are excluded unless asked for.

Parameters

store_idstring · uuidqueryrequired

The store whose catalogue to read (must be the authenticated store).

include_archivedbooleanqueryoptional

Include archived services. Defaults to active only.

Response, 200

servicesarray of objectrequired
22 child fields
idstringrequired
store_idstringrequired
categorystring, nullablerequired
namestringrequired
descriptionstring, nullablerequired
imagesarray of stringrequired

Image URLs the merchant uploaded for this service.

duration_minnumberrequired
price_pencenumberrequired
currencystringrequired
price_typeenumrequired
fixedfromper_person
party_minnumberrequired
party_maxnumberrequired
buffer_before_minnumberrequired
buffer_after_minnumberrequired
processing_minnumberrequired
location_typeenumrequired
on_premisesonlinecustomer_site
rebook_interval_daysnumber, nullablerequired
online_visiblebooleanrequired
sortnumberrequired
is_activebooleanrequired
variantsarray of objectrequired

Pricing/duration variants for this service.

6 child fields
idstringrequired
namestringrequired
duration_minnumber, nullablerequired
price_pencenumber, nullablerequired
sortnumberrequired
is_activebooleanrequired
addonsarray of objectrequired

Optional paid add-ons that extend this service.

6 child fields
idstringrequired
namestringrequired
extra_minnumberrequired
extra_pencenumberrequired
sortnumberrequired
is_activebooleanrequired
curl -G "https://www.membber.com/api/v1/bookings/services" \
  -H "Authorization: Bearer $MEMBBER_TOKEN" \
  --data-urlencode "store_id=6659c139-0000-4000-8000-d0c500000066"
Response, 200
{
  "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
        }
      ]
    }
  ]
}

Create a bookable service.

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.

Request body

store_idstring · uuidrequired
client_tokenstringoptional

Caller-generated idempotency key. Send the same token on a retry to get the SAME service back instead of a duplicate.

categorystring, nullableoptional
namestringrequired

What the customer books.

descriptionstring, nullableoptional
imagesarray of string · urioptional

Uploaded image URLs.

duration_minintegerrequired

Appointment length in minutes.

price_penceintegeroptional

Price in the smallest currency unit.

currencystringoptional
price_typeenumoptional

'fixed' | 'from' | 'per_person'.

fixedfromper_person
party_minintegeroptional
party_maxintegeroptional
deposit_policyobjectoptional

Deposit policy JSON. WRITABLE but INERT until the money stage, no charge is wired from it here.

buffer_before_minintegeroptional

Prep time the unit is occupied before.

buffer_after_minintegeroptional

Clean-up time the unit is occupied after.

processing_minintegeroptional
location_typeenumoptional
on_premisesonlinecustomer_site
rebook_interval_daysinteger, nullableoptional
online_visiblebooleanoptional

Whether customers can see and book this online.

sortintegeroptional

Response, 200

serviceobjectrequired
22 child fields
idstringrequired
store_idstringrequired
categorystring, nullablerequired
namestringrequired
descriptionstring, nullablerequired
imagesarray of stringrequired

Image URLs the merchant uploaded for this service.

duration_minnumberrequired
price_pencenumberrequired
currencystringrequired
price_typeenumrequired
fixedfromper_person
party_minnumberrequired
party_maxnumberrequired
buffer_before_minnumberrequired
buffer_after_minnumberrequired
processing_minnumberrequired
location_typeenumrequired
on_premisesonlinecustomer_site
rebook_interval_daysnumber, nullablerequired
online_visiblebooleanrequired
sortnumberrequired
is_activebooleanrequired
variantsarray of objectrequired

Pricing/duration variants for this service.

6 child fields
idstringrequired
namestringrequired
duration_minnumber, nullablerequired
price_pencenumber, nullablerequired
sortnumberrequired
is_activebooleanrequired
addonsarray of objectrequired

Optional paid add-ons that extend this service.

6 child fields
idstringrequired
namestringrequired
extra_minnumberrequired
extra_pencenumberrequired
sortnumberrequired
is_activebooleanrequired
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
  }'
Response, 200
{
  "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
      }
    ]
  }
}

Update a service.

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.

Parameters

serviceIdstringpathrequired

Request body

store_idstring · uuidrequired
categorystring, nullableoptional
namestringoptional

What the customer books.

descriptionstring, nullableoptional
imagesarray of string · urioptional

Uploaded image URLs.

duration_minintegeroptional

Appointment length in minutes.

price_penceintegeroptional

Price in the smallest currency unit.

currencystringoptional
price_typeenumoptional

'fixed' | 'from' | 'per_person'.

fixedfromper_person
party_minintegeroptional
party_maxintegeroptional
deposit_policyobjectoptional

Deposit policy JSON. WRITABLE but INERT until the money stage, no charge is wired from it here.

buffer_before_minintegeroptional

Prep time the unit is occupied before.

buffer_after_minintegeroptional

Clean-up time the unit is occupied after.

processing_minintegeroptional
location_typeenumoptional
on_premisesonlinecustomer_site
rebook_interval_daysinteger, nullableoptional
online_visiblebooleanoptional

Whether customers can see and book this online.

sortintegeroptional
is_activebooleanoptional

Set true to RESTORE an archived service (the inverse of /archive); false archives it. Archiving never deletes, existing appointments keep their snapshot either way.

Response, 200

serviceobjectrequired
22 child fields
idstringrequired
store_idstringrequired
categorystring, nullablerequired
namestringrequired
descriptionstring, nullablerequired
imagesarray of stringrequired

Image URLs the merchant uploaded for this service.

duration_minnumberrequired
price_pencenumberrequired
currencystringrequired
price_typeenumrequired
fixedfromper_person
party_minnumberrequired
party_maxnumberrequired
buffer_before_minnumberrequired
buffer_after_minnumberrequired
processing_minnumberrequired
location_typeenumrequired
on_premisesonlinecustomer_site
rebook_interval_daysnumber, nullablerequired
online_visiblebooleanrequired
sortnumberrequired
is_activebooleanrequired
variantsarray of objectrequired

Pricing/duration variants for this service.

6 child fields
idstringrequired
namestringrequired
duration_minnumber, nullablerequired
price_pencenumber, nullablerequired
sortnumberrequired
is_activebooleanrequired
addonsarray of objectrequired

Optional paid add-ons that extend this service.

6 child fields
idstringrequired
namestringrequired
extra_minnumberrequired
extra_pencenumberrequired
sortnumberrequired
is_activebooleanrequired
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"
  }'
Response, 200
{
  "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
      }
    ]
  }
}

Add a service add-on.

Adds an add-on to a service the authenticated store owns. Idempotent per `client_token`.

Parameters

serviceIdstringpathrequired

Request body

store_idstring · uuidrequired
client_tokenstringoptional

Caller-generated idempotency key. Send the same token on a retry to get the SAME service back instead of a duplicate.

namestringrequired
extra_minintegeroptional

Extra minutes this add-on adds to the appointment.

extra_penceintegeroptional

Extra charge in the smallest currency unit (INERT until the money stage).

sortintegeroptional

Response, 200

addonobjectrequired
6 child fields
idstringrequired
namestringrequired
extra_minnumberrequired
extra_pencenumberrequired
sortnumberrequired
is_activebooleanrequired
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"
  }'
Response, 200
{
  "addon": {
    "id": "00000d1b-0000-4000-8000-d0c500000000",
    "name": "Example name",
    "extra_min": 1,
    "extra_pence": 1500,
    "sort": 1,
    "is_active": true
  }
}

Update or archive a service add-on.

Partial update of an add-on belonging to a service the authenticated store owns.

Parameters

serviceIdstringpathrequired
addonIdstringpathrequired

Request body

store_idstring · uuidrequired
namestringoptional
extra_minintegeroptional
extra_penceintegeroptional
sortintegeroptional
is_activebooleanoptional

Set false to archive this add-on.

Response, 200

addonobjectrequired
6 child fields
idstringrequired
namestringrequired
extra_minnumberrequired
extra_pencenumberrequired
sortnumberrequired
is_activebooleanrequired
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"
  }'
Response, 200
{
  "addon": {
    "id": "00000d1b-0000-4000-8000-d0c500000000",
    "name": "Example name",
    "extra_min": 1,
    "extra_pence": 1500,
    "sort": 1,
    "is_active": true
  }
}

Archive a service (honouring existing bookings).

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.

Parameters

serviceIdstringpathrequired

Request body

store_idstring · uuidrequired

Response, 200

service_idstringrequired
archivedbooleanrequired

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"
  }'
Response, 200
{
  "service_id": "993232e5-0000-4000-8000-d0c500000099",
  "archived": true
}

Add a service variant.

Adds a variant to a service the authenticated store owns. Idempotent per `client_token`.

Parameters

serviceIdstringpathrequired

Request body

store_idstring · uuidrequired
client_tokenstringoptional

Caller-generated idempotency key. Send the same token on a retry to get the SAME service back instead of a duplicate.

namestringrequired
duration_mininteger, nullableoptional

Overrides the service duration for this variant.

price_penceinteger, nullableoptional
sortintegeroptional

Response, 200

variantobjectrequired
6 child fields
idstringrequired
namestringrequired
duration_minnumber, nullablerequired
price_pencenumber, nullablerequired
sortnumberrequired
is_activebooleanrequired
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"
  }'
Response, 200
{
  "variant": {
    "id": "00000d1b-0000-4000-8000-d0c500000000",
    "name": "Example name",
    "duration_min": 1,
    "price_pence": 1500,
    "sort": 1,
    "is_active": true
  }
}

Update or archive a service variant.

Partial update of a variant belonging to a service the authenticated store owns.

Parameters

serviceIdstringpathrequired
variantIdstringpathrequired

Request body

store_idstring · uuidrequired
namestringoptional
duration_mininteger, nullableoptional
price_penceinteger, nullableoptional
sortintegeroptional
is_activebooleanoptional

Set false to archive this variant.

Response, 200

variantobjectrequired
6 child fields
idstringrequired
namestringrequired
duration_minnumber, nullablerequired
price_pencenumber, nullablerequired
sortnumberrequired
is_activebooleanrequired
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"
  }'
Response, 200
{
  "variant": {
    "id": "00000d1b-0000-4000-8000-d0c500000000",
    "name": "Example name",
    "duration_min": 1,
    "price_pence": 1500,
    "sort": 1,
    "is_active": true
  }
}

Set the store timezone.

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.

Request body

store_idstring · uuidrequired
timezonestringrequired

An IANA timezone name, e.g. "Europe/London". Validated against the runtime before it is written.

Response, 200

store_idstringrequired
timezonestringrequired
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"
  }'
Response, 200
{
  "store_id": "6659c139-0000-4000-8000-d0c500000066",
  "timezone": "Europe/London"
}

What a store offers for online booking.

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.

auth none, publicop getBookingStorefrontrate limitederrors

Parameters

store_idstring · uuidqueryrequired

The store whose bookable offering to read.

Response, 200

servicesarray of objectrequired

Bookable online, in the merchant's own order.

14 child fields
idstringrequired
namestringrequired
descriptionstring, nullablerequired
imagesarray of stringrequired

The merchant's own photos. Empty is a real state, the client draws its designed blank, never a stock photo.

duration_minnumberrequired

What the customer is told the appointment lasts. Buffers are NEVER exposed, they are the shop's business, not the customer's.

price_pencenumberrequired
currencystringrequired
price_typeenumrequired

'from' when performers price it differently, the exact figure locks at the person step.

fixedfromper_person
categorystring, nullablerequired
unit_idsarray of stringrequired
sortnumberrequired
party_minnumberrequired

Smallest party this service takes. 1 for a normal appointment.

party_maxnumberrequired

Largest party bookable online. Above it, the client offers "message the restaurant". For a restaurant this rises to what the FLOOR can actually seat: declaring that two tables push together is the merchant saying "I will seat that party", so the join sets the ceiling and there is no second number to keep in step. It never falls below what the merchant set by hand.

seat_choice_levelnumberrequired

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).

peoplearray of objectrequired

Who can be booked, in the merchant's own order, the same order the diary columns use.

5 child fields
idstringrequired

The bookable unit id, what `availability` and the booking call take as `unit_id`.

display_namestringrequired
photo_urlstring, nullablerequired
sortnumberrequired
overridesarray of objectrequired

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.

3 child fields
service_idstringrequired
duration_minnumber, nullablerequired
price_pencenumber, nullablerequired
areasarray of objectrequired

The venue's dining areas, in the merchant's order. Empty unless it is a tables store with areas.

4 child fields
idstringrequired
namestringrequired

The area name the diner picks, e.g. "Window", "Main room", "Bar".

sortnumberrequired
descriptionstring, nullablerequired

Optional one line, e.g. "Heated and covered".

acceptingbooleanrequired

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.

catalogue_onlybooleanrequired

True when the shop has PUBLISHED a price list but nobody can perform any of it online, so `services` is what it charges and `people` is empty. The client renders the list as an untappable price list under one calm line instead of hiding the shop entirely. False whenever anything here is bookable, and then `services` holds only what is genuinely bookable, exactly as before.

curl -G "https://www.membber.com/api/v1/bookings/storefront" \
  --data-urlencode "store_id=6659c139-0000-4000-8000-d0c500000066"
Response, 200
{
  "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,
  "catalogue_only": true
}

A store's bookable units.

Every bookable unit for the authenticated store, each carrying which services it offers. Archived units excluded unless asked for.

Parameters

store_idstring · uuidqueryrequired
include_archivedbooleanqueryoptional

Response, 200

unitsarray of objectrequired
20 child fields
idstringrequired
store_idstringrequired
kindenumrequired
personspaceasset
unit_typestring, nullablerequired

Free-text sub-type (e.g. "chair", "room").

user_idstring, nullablerequired

The staff member this unit represents, when kind = person.

display_namestringrequired
biostring, nullablerequired
photo_urlstring, nullablerequired
colourstring, nullablerequired

Diary column colour.

bookablebooleanrequired

Whether the unit can hold appointments at all.

online_bookablebooleanrequired

Whether customers can book it online (vs staff-only).

sortnumberrequired
is_activebooleanrequired
servicesarray of objectrequired

Which services this unit offers, with any per-unit overrides.

3 child fields
service_idstringrequired
duration_override_minnumber, nullablerequired
price_override_pencenumber, nullablerequired
area_idstring, nullablerequired

The dining AREA this table stands in. Null for every non-restaurant unit and for a table nobody has filed.

area_namestring, nullablerequired

Resolved name of area_id ("Dining room", "Terrace"), so the joins screen can group a floor into its rooms.

seats_minnumber, nullablerequired

Smallest party this table seats. Null for a person or an asset.

seats_maxnumber, nullablerequired

Largest party this table seats. Two of these added together is what makes a party of eight bookable at a six-seat restaurant.

joins_witharray of stringrequired

The other tables this one physically pushes together with. SYMMETRIC, if Table 5 lists Table 6, Table 6 lists Table 5, and the server keeps it that way. Empty means "this table stands alone", which is the honest default: only the merchant knows their floor.

joins_are_assumedbooleanrequired

true when the joins above were DERIVED for a demo store rather than declared by the merchant, the client says so plainly above the list. Saving the screen makes them the merchant's own answer and this goes false.

curl -G "https://www.membber.com/api/v1/bookings/units" \
  -H "Authorization: Bearer $MEMBBER_TOKEN" \
  --data-urlencode "store_id=6659c139-0000-4000-8000-d0c500000066"
Response, 200
{
  "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
        }
      ],
      "area_id": "d381ba0d-0000-4000-8000-d0c5000000d3",
      "area_name": "<area_name>",
      "seats_min": 1,
      "seats_max": 1,
      "joins_with": [
        "<joins_with>"
      ],
      "joins_are_assumed": true
    }
  ]
}

Create a bookable unit.

Adds a person / space / asset to the authenticated store. Idempotent per `client_token`.

Request body

store_idstring · uuidrequired
client_tokenstringoptional

Caller-generated idempotency key, a retry with the same token returns the SAME unit, never a duplicate.

kindenumoptional

'person' | 'space' | 'asset'. Defaults to 'person'.

personspaceasset
unit_typestring, nullableoptional
user_idstring · uuid, nullableoptional

Only permitted when kind = person (the DB enforces this).

display_namestringrequired

What the diary column is labelled.

biostring, nullableoptional
photo_urlstring · uri, nullableoptional
colourstring, nullableoptional
bookablebooleanoptional
online_bookablebooleanoptional
sortintegeroptional

Response, 200

unitobjectrequired
20 child fields
idstringrequired
store_idstringrequired
kindenumrequired
personspaceasset
unit_typestring, nullablerequired

Free-text sub-type (e.g. "chair", "room").

user_idstring, nullablerequired

The staff member this unit represents, when kind = person.

display_namestringrequired
biostring, nullablerequired
photo_urlstring, nullablerequired
colourstring, nullablerequired

Diary column colour.

bookablebooleanrequired

Whether the unit can hold appointments at all.

online_bookablebooleanrequired

Whether customers can book it online (vs staff-only).

sortnumberrequired
is_activebooleanrequired
servicesarray of objectrequired

Which services this unit offers, with any per-unit overrides.

3 child fields
service_idstringrequired
duration_override_minnumber, nullablerequired
price_override_pencenumber, nullablerequired
area_idstring, nullablerequired

The dining AREA this table stands in. Null for every non-restaurant unit and for a table nobody has filed.

area_namestring, nullablerequired

Resolved name of area_id ("Dining room", "Terrace"), so the joins screen can group a floor into its rooms.

seats_minnumber, nullablerequired

Smallest party this table seats. Null for a person or an asset.

seats_maxnumber, nullablerequired

Largest party this table seats. Two of these added together is what makes a party of eight bookable at a six-seat restaurant.

joins_witharray of stringrequired

The other tables this one physically pushes together with. SYMMETRIC, if Table 5 lists Table 6, Table 6 lists Table 5, and the server keeps it that way. Empty means "this table stands alone", which is the honest default: only the merchant knows their floor.

joins_are_assumedbooleanrequired

true when the joins above were DERIVED for a demo store rather than declared by the merchant, the client says so plainly above the list. Saving the screen makes them the merchant's own answer and this goes false.

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>"
  }'
Response, 200
{
  "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
      }
    ],
    "area_id": "d381ba0d-0000-4000-8000-d0c5000000d3",
    "area_name": "<area_name>",
    "seats_min": 1,
    "seats_max": 1,
    "joins_with": [
      "<joins_with>"
    ],
    "joins_are_assumed": true
  }
}

Update or archive a bookable unit.

Partial update of a unit the authenticated store owns. Archiving (is_active=false) keeps existing appointments intact.

Parameters

unitIdstringpathrequired

Request body

store_idstring · uuidrequired
kindenumoptional

'person' | 'space' | 'asset'. Defaults to 'person'.

personspaceasset
unit_typestring, nullableoptional
user_idstring · uuid, nullableoptional

Only permitted when kind = person (the DB enforces this).

display_namestringoptional

What the diary column is labelled.

biostring, nullableoptional
photo_urlstring · uri, nullableoptional
colourstring, nullableoptional
bookablebooleanoptional
online_bookablebooleanoptional
sortintegeroptional
is_activebooleanoptional

Set false to archive the unit (existing appointments are honoured).

Response, 200

unitobjectrequired
20 child fields
idstringrequired
store_idstringrequired
kindenumrequired
personspaceasset
unit_typestring, nullablerequired

Free-text sub-type (e.g. "chair", "room").

user_idstring, nullablerequired

The staff member this unit represents, when kind = person.

display_namestringrequired
biostring, nullablerequired
photo_urlstring, nullablerequired
colourstring, nullablerequired

Diary column colour.

bookablebooleanrequired

Whether the unit can hold appointments at all.

online_bookablebooleanrequired

Whether customers can book it online (vs staff-only).

sortnumberrequired
is_activebooleanrequired
servicesarray of objectrequired

Which services this unit offers, with any per-unit overrides.

3 child fields
service_idstringrequired
duration_override_minnumber, nullablerequired
price_override_pencenumber, nullablerequired
area_idstring, nullablerequired

The dining AREA this table stands in. Null for every non-restaurant unit and for a table nobody has filed.

area_namestring, nullablerequired

Resolved name of area_id ("Dining room", "Terrace"), so the joins screen can group a floor into its rooms.

seats_minnumber, nullablerequired

Smallest party this table seats. Null for a person or an asset.

seats_maxnumber, nullablerequired

Largest party this table seats. Two of these added together is what makes a party of eight bookable at a six-seat restaurant.

joins_witharray of stringrequired

The other tables this one physically pushes together with. SYMMETRIC, if Table 5 lists Table 6, Table 6 lists Table 5, and the server keeps it that way. Empty means "this table stands alone", which is the honest default: only the merchant knows their floor.

joins_are_assumedbooleanrequired

true when the joins above were DERIVED for a demo store rather than declared by the merchant, the client says so plainly above the list. Saving the screen makes them the merchant's own answer and this goes false.

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"
  }'
Response, 200
{
  "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
      }
    ],
    "area_id": "d381ba0d-0000-4000-8000-d0c5000000d3",
    "area_name": "<area_name>",
    "seats_min": 1,
    "seats_max": 1,
    "joins_with": [
      "<joins_with>"
    ],
    "joins_are_assumed": true
  }
}

Say which tables push together.

Records that this table can be pushed together with the tables you name, so a party too big for any single table can still be seated. Kept SYMMETRIC by the server: declaring Table 5 joins Table 6 also makes Table 6 join Table 5, and removing it removes both directions. Replaces the whole set atomically and is idempotent. Only a `space` unit can join another (joining two barbers is not a thing). Un-joining never disturbs a booking that already occupies both tables, that occupancy is recorded on the booking, not here; this only governs what may be offered next.

Parameters

unitIdstringpathrequired

Request body

store_idstring · uuidrequired
joins_witharray of string · uuidrequired

The COMPLETE set of tables this one pushes together with. Send an empty array to say it stands alone. Each must be an active space in the same store; a table can never join itself.

Response, 200

unit_idstringrequired
joins_witharray of stringrequired

The set that now applies, on this table and symmetrically on each of theirs.

curl -X PUT "https://www.membber.com/api/v1/bookings/units/cde691bf-0000-4000-8000-d0c5000000cd/joins" \
  -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",
    "joins_with": [
      "900d8ebc-0000-4000-8000-d0c500000090"
    ]
  }'
Response, 200
{
  "unit_id": "eeebf9b6-0000-4000-8000-d0c5000000ee",
  "joins_with": [
    "<joins_with>"
  ]
}

Map a service onto a unit.

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.

Parameters

unitIdstringpathrequired

Request body

store_idstring · uuidrequired
service_idstring · uuidrequired

The service this unit should offer (must belong to the same store).

duration_override_mininteger, nullableoptional

Per-unit duration override.

price_override_penceinteger, nullableoptional

Per-unit price override.

Response, 200

unit_idstringrequired
service_idstringrequired
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"
  }'
Response, 200
{
  "unit_id": "eeebf9b6-0000-4000-8000-d0c5000000ee",
  "service_id": "993232e5-0000-4000-8000-d0c500000099"
}

Unmap a service from a unit.

Removes the (unit, service) mapping. Idempotent, removing one that is already gone is a no-op.

Parameters

unitIdstringpathrequired
serviceIdstringpathrequired
store_idstring · uuidqueryrequired

Response, 200

unit_idstringrequired
service_idstringrequired
removedbooleanrequired

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"
Response, 200
{
  "unit_id": "eeebf9b6-0000-4000-8000-d0c5000000ee",
  "service_id": "993232e5-0000-4000-8000-d0c500000099",
  "removed": true
}

A unit's working pattern.

The working intervals for a unit the authenticated store owns. Multiple rows for one weekday ARE the intervals; the gaps ARE the breaks.

auth merchant:readop listUnitWorkingPatternsrate limitederrors

Parameters

unitIdstringpathrequired
store_idstring · uuidqueryrequired

Response, 200

unit_idstringrequired
patternsarray of objectrequired
7 child fields
weekdayintegerrequired

0 = Sunday … 6 = Saturday.

start_minuteintegerrequired

Minutes past midnight the interval starts.

end_minuteintegerrequired

Minutes past midnight the interval ends (must be after start).

spans_midnightbooleanoptional

True for an interval that runs past midnight.

effective_fromstring, nullableoptional

YYYY-MM-DD the interval starts applying (null = always).

effective_tostring, nullableoptional

YYYY-MM-DD the interval stops applying (null = open-ended).

idstringrequired
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"
Response, 200
{
  "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"
    }
  ]
}

Replace a unit's working pattern.

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.

Parameters

unitIdstringpathrequired

Request body

store_idstring · uuidrequired
patternsarray of objectrequired

The COMPLETE new set of intervals for this unit. Send an empty array to clear the pattern.

6 child fields
weekdayintegerrequired

0 = Sunday … 6 = Saturday.

start_minuteintegerrequired

Minutes past midnight the interval starts.

end_minuteintegerrequired

Minutes past midnight the interval ends (must be after start).

spans_midnightbooleanoptional

True for an interval that runs past midnight.

effective_fromstring, nullableoptional

YYYY-MM-DD the interval starts applying (null = always).

effective_tostring, nullableoptional

YYYY-MM-DD the interval stops applying (null = open-ended).

Response, 200

unit_idstringrequired
patternsarray of objectrequired
7 child fields
weekdayintegerrequired

0 = Sunday … 6 = Saturday.

start_minuteintegerrequired

Minutes past midnight the interval starts.

end_minuteintegerrequired

Minutes past midnight the interval ends (must be after start).

spans_midnightbooleanoptional

True for an interval that runs past midnight.

effective_fromstring, nullableoptional

YYYY-MM-DD the interval starts applying (null = always).

effective_tostring, nullableoptional

YYYY-MM-DD the interval stops applying (null = open-ended).

idstringrequired
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
      }
    ]
  }'
Response, 200
{
  "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"
    }
  ]
}
WhatsApp
Book a Call
Start Free