16 operations. Every schema and example on this page is generated from the platform contract.
Cross-store ACTIVE collect legs for the wallet Collections hub. One row per in-flight pickup (paid, not collected/cancelled) across every store the customer holds. Caller-only, index-backed, light by design so it is cheap to poll while a pickup is in flight.
The collect leg's own guest_orders id (child_collect for a mixed leg, else the standalone order id).
The order the customer owns (parent_order_id for a mixed leg, else leg_id).
Display order number (the parent's for a mixed leg).
Store has live chat → the tile can offer "Message <store>".
none | accepted | preparing | ready (active legs are never collected/cancelled).
What this leg's QR encodes: the leg identity, HMAC-signed. The short collection_code is a human label and is NOT sufficient to collect.
'now' (ASAP) | 'scheduled' (booked slot).
ISO instant of the booked slot (scheduled legs); null for ASAP.
curl -G "https://www.membber.com/api/v1/customer/active-collections" \
-H "Authorization: Bearer $MEMBBER_TOKEN"import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.GET("/api/v1/customer/active-collections");
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.getActiveCollections().ok.body.json
print(response){
"legs": [
{
"leg_id": "be0aecec-0000-4000-8000-d0c5000000be",
"order_id": "4991ffac-0000-4000-8000-d0c500000049",
"order_number": "<order_number>",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"store_name": "<store_name>",
"store_logo_url": "https://example.com/image.jpg",
"store_logo_blurhash": "<store_logo_blurhash>",
"store_primary_color": "<store_primary_color>",
"store_secondary_color": "<store_secondary_color>",
"messaging_enabled": true,
"fulfilment_status": "<fulfilment_status>",
"collection_code": "EXAMPLE10",
"collect_token": "<collect_token>",
"prep_minutes": 1,
"collection_mode": "<collection_mode>",
"requested_collection_at": "<requested_collection_at>",
"accepted_at": "<accepted_at>",
"preparing_at": "<preparing_at>",
"ready_at": "<ready_at>",
"created_at": "<created_at>",
"paid_at": "<paid_at>",
"currency": "GBP",
"total_pence": 1500,
"refunded_pence": 1500,
"item_count": 1,
"items": [
{
"name": "Example name",
"quantity": 1
}
]
}
]
}The signed-in customer's own booking for a specific class occurrence, with the occurrence and class context the iOS ClassBooking decoder needs (schedule, instructor, capacity, class details, promotion / reconfirmation state). Read-only. Only the caller own booking is ever returned; cancelled rows are excluded.
Booking id.
Booking status (waitlisted / promotion_pending / confirmed).
Rank in the waitlist queue, if waitlisted.
Whether the waitlist position is locked.
Date the booking was made.
Row creation timestamp.
The class occurrence this booking is for.
Occurrence scheduled date.
Occurrence start time.
Occurrence end time.
Instructor for the occurrence.
Occurrence maximum capacity.
Number of confirmed bookings on the occurrence.
Occurrence status.
Class name.
Class category.
Class description.
Class image URL.
Class difficulty.
Class duration in minutes.
When a pending promotion offer expires.
Whether reconfirmation is required.
Reconfirmation deadline.
When the booking was reconfirmed.
curl -G "https://www.membber.com/api/v1/customer/bookings/by-occurrence/a987c2cc-0000-4000-8000-d0c5000000a9" \
-H "Authorization: Bearer $MEMBBER_TOKEN"import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.GET("/api/v1/customer/bookings/by-occurrence/{occurrenceId}", {
params: { path: { occurrenceId: "a987c2cc-0000-4000-8000-d0c5000000a9" } },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.getCustomerBookingByOccurrence(
path: .init(occurrenceId: "a987c2cc-0000-4000-8000-d0c5000000a9")
).ok.body.json
print(response){
"booking": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"status": "<status>",
"waitlistPosition": 1,
"waitlistLocked": true,
"bookingDate": "<bookingDate>",
"createdAt": "<createdAt>",
"occurrenceId": "a987c2cc-0000-4000-8000-d0c5000000a9",
"scheduledDate": "<scheduledDate>",
"startTime": "<startTime>",
"endTime": "<endTime>",
"instructorName": "<instructorName>",
"maxCapacity": 1,
"confirmedCount": 1,
"occurrenceStatus": "<occurrenceStatus>",
"className": "<className>",
"category": "<category>",
"description": "Added at the front desk",
"imageUrl": "https://example.com/image.jpg",
"difficulty": "<difficulty>",
"durationMinutes": 1,
"promotionExpiresAt": "<promotionExpiresAt>",
"reconfirmationRequired": true,
"reconfirmationDeadline": "<reconfirmationDeadline>",
"reconfirmedAt": "<reconfirmedAt>"
}
}The signed-in customer cancels one of their own class bookings or leaves a waitlist. Flips status + releases the seat + re-numbers the waitlist + cascades any pending promotion (cancel_class_booking), then classifies the drop-in refund early-vs-late (store-timezone aware) and issues it on the connected account. Idempotent at the service layer: the refund is deduped per (booking, trigger) and an already-cancelled booking is rejected, so a retry never double-refunds or double-releases a seat.
The caller own booking to cancel / leave. Ownership is enforced server-side against the session.
The booking was cancelled / the waitlist was left.
The drop-in refund outcome for this cancellation (membership-paid bookings report a no-payment skip).
Refund outcome (succeeded / already_succeeded / skipped_no_payment / failed / ...).
How the refund was classified: voluntary_leave_early (full) or voluntary_leave_late (minus fee).
Amount refunded to the member, in pence.
Amount withheld (late-leave fee), in pence.
Stripe refund id, when a refund was actually issued.
Non-fatal refund error message, if the refund engine failed.
curl -X POST "https://www.membber.com/api/v1/customer/bookings/cancel" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"booking_id": "ae11dec1-0000-4000-8000-d0c5000000ae"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/customer/bookings/cancel", {
body: {
booking_id: "ae11dec1-0000-4000-8000-d0c5000000ae"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.cancelClassBooking(
body: .json(.init(
bookingId: "ae11dec1-0000-4000-8000-d0c5000000ae"
))
).ok.body.json
print(response){
"left": true,
"refund": {
"status": "<status>",
"triggerReason": "Added at the front desk",
"amountRefundedPence": 1500,
"amountWithheldPence": 1500,
"stripeRefundId": "41b31220-0000-4000-8000-d0c500000041",
"errorMessage": "Added at the front desk"
}
}Soft 5-minute stock hold for a customer basket. On add/quantity change the client sets the hold to the new quantity and reads back `granted` (so two shoppers can't both hold the last unit); on basket clear it releases every hold. Holds lapse on their own after 5 minutes. Idempotent, a set replaces (never stacks) the cart's hold.
Opaque per-basket token the client generates.
true → release every hold for this cart (item_id/quantity ignored).
Stock-tracked menu item to hold (UUID). Required unless clear=true.
Desired hold quantity (clamped 0..99). Required unless clear=true.
false when the item isn't stock-tracked (nothing held).
How much of the requested quantity was actually held (the client caps the basket to this).
Units still available to this cart after the grant (null when untracked).
How many holds were released (clear mode only).
curl -X POST "https://www.membber.com/api/v1/customer/cart-hold" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"cart_token": "<cart_token>"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/customer/cart-hold", {
body: {
cart_token: "<cart_token>"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.setCartHold(
body: .json(.init(
cartToken: "<cart_token>"
))
).ok.body.json
print(response){
"tracked": true,
"granted": 1,
"available": 1,
"cleared": 1
}Customer mini-store collection slot picker: the open slots for the date (with tz-correct start instants) and, per collectable menu item, the earliest instant it can be collected (now + its lead time). The app greys an item for a chosen slot when earliest_collectable_at > slot.start_at.
Store whose collection day-view to fetch.
Collection date (YYYY-MM-DD, interpreted in the store timezone).
The requested date (echoed).
HH:MM
HH:MM
ISO instant (store-tz aware).
ISO instant = now + max(item prep, store default). Grey the item for a slot when this > slot.start_at.
curl -G "https://www.membber.com/api/v1/customer/collection-slots" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
--data-urlencode "store_id=6659c139-0000-4000-8000-d0c500000066" \
--data-urlencode "date=<date>"import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.GET("/api/v1/customer/collection-slots", {
params: { query: { store_id: "6659c139-0000-4000-8000-d0c500000066", date: "<date>" } },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.getCollectionDay(
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066", date: "<date>")
).ok.body.json
print(response){
"date": "<date>",
"slots": [
{
"id": "00000d1b-0000-4000-8000-d0c500000000",
"start_time": "<start_time>",
"end_time": "<end_time>",
"orders_remaining": 1,
"max_orders": 1,
"full": true,
"start_at": "<start_at>"
}
],
"items": [
{
"id": "00000d1b-0000-4000-8000-d0c500000000",
"collect_eligible": true,
"earliest_collectable_at": "<earliest_collectable_at>"
}
]
}The customer app's "Your agreements" screen: every consent decision the member has recorded, across all of their stores plus the platform-scope Terms/Privacy, grouped by (agreement type, store) with each store's name, the full history newest first, and an up-to-date flag versus the active definition. The customer is always resolved from the session, never a client-supplied id.
acceptedrejectedwithdrawnacceptedrejectedwithdrawncurl -G "https://www.membber.com/api/v1/customer/consent/mine" \
-H "Authorization: Bearer $MEMBBER_TOKEN"import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.GET("/api/v1/customer/consent/mine");
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.getCustomerConsents().ok.body.json
print(response){
"groups": [
{
"agreement_type": "<agreement_type>",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"store_name": "<store_name>",
"latest_decision": "accepted",
"latest_version": 1,
"latest_accepted_at": "<latest_accepted_at>",
"active_version": 1,
"up_to_date": true,
"events": [
{
"id": "00000d1b-0000-4000-8000-d0c500000000",
"decision": "accepted",
"agreement_version": 1,
"accepted_at": "<accepted_at>",
"source": "<source>",
"terms_snapshot": "<terms_snapshot>",
"terms_url": "https://example.com/image.jpg",
"ip_address": "<ip_address>",
"user_agent": "<user_agent>",
"signature_image_url": "https://example.com/image.jpg",
"metadata": {}
}
]
}
]
}Records a customer accept / reject / withdraw decision against the active definition for a platform agreement type (Terms, Privacy, marketing), freezing the wording at the version validated. Powers the onboarding "Before you start" gate and the "Your agreements" withdraw affordance. The customer is always resolved from the session, never a client-supplied id. Appends to the immutable consent_events ledger.
Agreement type to record against. One of: platform_terms, platform_privacy, marketing_email.
The member's decision. One of: accepted, rejected, withdrawn.
Consent scope: the store the agreement belongs to (null / omitted = platform scope). NOT an identity field.
If supplied, must match the active definition hash (else CONSENT_STALE 409), guards a TOCTOU where the wording changed after the client fetched it.
curl -X POST "https://www.membber.com/api/v1/customer/consent/record" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"agreement_type": "<agreement_type>",
"decision": "<decision>"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/customer/consent/record", {
body: {
agreement_type: "<agreement_type>",
decision: "<decision>"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.recordConsentDecision(
body: .json(.init(
agreementType: "<agreement_type>",
decision: "<decision>"
))
).ok.body.json
print(response){
"event_id": "1093c0e0-0000-4000-8000-d0c500000010",
"version": 1
}Drives the onboarding "Before you start" gate: the platform Terms and Privacy (required, blocking) and the marketing opt-in (optional), each with whether the signed-in customer already satisfies it at the current version. needsGate is true when any required agreement is unsatisfied. The customer is always resolved from the session, never a client-supplied id.
member has a valid current-version ACCEPTANCE
member made ANY current-version decision (accepted OR rejected), so a declined optional (marketing) is not re-asked as if never seen
curl -G "https://www.membber.com/api/v1/customer/consent/required" \
-H "Authorization: Bearer $MEMBBER_TOKEN"import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.GET("/api/v1/customer/consent/required");
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.getRequiredConsents().ok.body.json
print(response){
"needsGate": true,
"consents": [
{
"agreement_type": "<agreement_type>",
"definition_id": "bc15d787-0000-4000-8000-d0c5000000bc",
"version": 1,
"terms_text": "<terms_text>",
"terms_url": "https://example.com/image.jpg",
"terms_hash": "<terms_hash>",
"required": true,
"satisfied": true,
"answered": true
}
]
}The customer app's emergency-contact save: upserts the store-scoped member_emergency_contacts row (insert when no id is supplied, update the customer-scoped row when an id is), then recomputes onboarding progress (non-fatal, a failure is logged, never surfaced). Returns the saved contact. Idempotency is rpc-mode: the update path is idempotent (scoped by id + customer_id, converges to the same row on retry); the create path has no HTTP dedup, exactly matching the legacy route which sent no Idempotency-Key. The customer is always resolved from the session, never a client-supplied id.
Store the emergency contact belongs to (the contact is store-scoped).
Existing contact id to update; omit to create a new contact.
Emergency contact's full name (required).
Emergency contact's phone number (required).
Relationship to the member (e.g. Partner, Parent).
Whether this is the primary emergency contact (defaults to false).
member_emergency_contacts row id.
The signed-in customer id (owner of the contact).
Store the contact belongs to.
curl -X POST "https://www.membber.com/api/v1/customer/emergency-contacts" \
-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",
"contact_name": "<contact_name>",
"contact_phone": "+44 7700 900123"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/customer/emergency-contacts", {
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
contact_name: "<contact_name>",
contact_phone: "+44 7700 900123"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.upsertEmergencyContact(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
contactName: "<contact_name>",
contactPhone: "+44 7700 900123"
))
).ok.body.json
print(response){
"contact": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"customer_id": "96607d1c-0000-4000-8000-d0c500000096",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"contact_name": "<contact_name>",
"contact_phone": "+44 7700 900123",
"relationship": "<relationship>",
"is_primary": true,
"created_at": "<created_at>",
"updated_at": "<updated_at>"
}
}A store's active, upcoming special events sorted by date, each enriched with an is_booked flag set from the signed-in customer's own confirmed bookings. Read-only. The customer is resolved from the session; a client-supplied id is never trusted for identity.
Store whose upcoming events to list.
Event id.
Store the event belongs to.
Event name.
Event description.
Event date (YYYY-MM-DD).
Event start time.
Event end time, if set.
Event location, if set.
Ticket price in pence (0 = free).
ISO currency code for the price.
Capacity cap, if any.
Whether the event is restricted to members.
Whether the event is active.
Event image URL, if set.
Merchant user who created the event, if recorded.
Row creation timestamp.
Row last-update timestamp.
True when the signed-in customer holds a confirmed booking for this event.
curl -G "https://www.membber.com/api/v1/customer/events" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
--data-urlencode "store_id=6659c139-0000-4000-8000-d0c500000066"import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.GET("/api/v1/customer/events", {
params: { query: { store_id: "6659c139-0000-4000-8000-d0c500000066" } },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.getCustomerUpcomingEvents(
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066")
).ok.body.json
print(response){
"events": [
{
"id": "00000d1b-0000-4000-8000-d0c500000000",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"name": "Example name",
"description": "Added at the front desk",
"event_date": "<event_date>",
"start_time": "<start_time>",
"end_time": "<end_time>",
"location": "<location>",
"price_pence": 1500,
"currency": "GBP",
"max_capacity": 1,
"is_members_only": true,
"is_active": true,
"image_url": "https://example.com/image.jpg",
"created_by": "<created_by>",
"created_at": "<created_at>",
"updated_at": "<updated_at>",
"is_booked": true
}
]
}The Membber Customer iOS Orders list/detail feed: real persisted guest_orders scoped to the signed-in customer for one store. Caller-only, paid-only, keyset-paginated. details=false returns the LIGHT response for fast first render (summary rows, no thumbnails/collect children/loyalty aggregation).
Store whose order history to list.
Page size (default 20, max 50).
Keyset cursor ("<created_at>|<id>") from a prior next_cursor.
Anything other than a case-insensitive "false" → FULL response; "false" → LIGHT response (summary rows only, no thumbnails/children/loyalty). Default true.
Store branding + legal/receipt fields; null for an unknown store.
paid | ready | fulfilled | partially_refunded | refunded | disputed | cancelled
'buy_now' | 'collect'
Signed QR payload for a STANDALONE collect order; null for buy-now and mixed parents (each leg carries its own).
The store rate only when it verifiably matches this order's snapshot, else 0.
Joined from menu_items; null when the item was deleted.
reorderreceipttrackmessagereport'standalone' | 'parent'
What this leg's QR encodes: the leg identity, HMAC-signed. The short collection_code is a human label and is NOT sufficient to collect.
none | accepted | preparing | ready | collected | cancelled
Joined from menu_items; null when the item was deleted.
in_progress | ready | complete | cancelled | refunded
Pass as ?before= for the next page.
curl -G "https://www.membber.com/api/v1/customer/orders" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
--data-urlencode "store_id=6659c139-0000-4000-8000-d0c500000066"import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.GET("/api/v1/customer/orders", {
params: { query: { store_id: "6659c139-0000-4000-8000-d0c500000066" } },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.listCustomerStoreOrders(
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066")
).ok.body.json
print(response){
"store": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"code": "EXAMPLE10",
"name": "Example name",
"logo_url": "https://example.com/image.jpg",
"primary_color": "<primary_color>",
"reward_title": "<reward_title>",
"address": "<address>",
"phone": "+44 7700 900123",
"email": "alex@example.com",
"vat_registered": true,
"vat_rate_percent": 1,
"vat_number": "<vat_number>"
},
"orders": [
{
"order_id": "4991ffac-0000-4000-8000-d0c500000049",
"order_number": "<order_number>",
"status": "<status>",
"order_type": "<order_type>",
"fulfilment_status": "<fulfilment_status>",
"collection_code": "EXAMPLE10",
"collect_token": "<collect_token>",
"prep_minutes": 1,
"collection_mode": "<collection_mode>",
"requested_collection_at": "<requested_collection_at>",
"currency": "GBP",
"subtotal_pence": 1500,
"discount_pence": 1500,
"promo_discount_pence": 1500,
"voucher_discount_pence": 1500,
"applied_promo_code": "EXAMPLE10",
"applied_promo_label": "<applied_promo_label>",
"tip_pence": 1500,
"service_charge_pence": 1500,
"vat_pence": 1500,
"vat_rate_percent": 1,
"total_pence": 1500,
"refunded_pence": 1500,
"created_at": "<created_at>",
"paid_at": "<paid_at>",
"accepted_at": "<accepted_at>",
"preparing_at": "<preparing_at>",
"ready_at": "<ready_at>",
"collected_at": "<collected_at>",
"item_count": 1,
"items": [
{
"menu_item_id": "ca65a6e7-0000-4000-8000-d0c5000000ca",
"name": "Example name",
"quantity": 1,
"unit_amount_pence": 1500,
"line_total_pence": 1500,
"image_url": "https://example.com/image.jpg"
}
],
"allowed_actions": [
"reorder"
],
"loyalty_awarded": true,
"stamps_earned": 1,
"voucher_earned": true,
"fulfilment_role": "<fulfilment_role>",
"handed_over_at": "<handed_over_at>",
"groups": [
{
"child_order_id": "f1ff3b6f-0000-4000-8000-d0c5000000f1",
"collection_code": "EXAMPLE10",
"collect_token": "<collect_token>",
"fulfilment_status": "<fulfilment_status>",
"prep_minutes": 1,
"collection_mode": "<collection_mode>",
"requested_collection_at": "<requested_collection_at>",
"accepted_at": "<accepted_at>",
"preparing_at": "<preparing_at>",
"ready_at": "<ready_at>",
"collected_at": "<collected_at>",
"total_pence": 1500,
"refunded_pence": 1500,
"item_count": 1,
"items": [
{
"menu_item_id": null,
"name": null,
"quantity": null,
"unit_amount_pence": null,
"line_total_pence": null,
"image_url": null
}
]
}
],
"overall_status": "<overall_status>"
}
],
"next_cursor": "<next_cursor>",
"has_more": true
}Creates a refund_requests row (status=requested) for the merchant to approve or reject. NO money moves here, approval is always a human merchant decision. Validates order ownership, refundability, the refund window, and a remaining balance; a duplicate open request 409s (idempotent via the unique index).
Store the order belongs to.
One of item_missing | wrong_item | quality | not_received | other.
Optional free-text detail from the customer.
Optional partial amount in pence; omit/null to request the full remaining balance.
curl -X POST "https://www.membber.com/api/v1/customer/orders/b80cf509-0000-4000-8000-d0c5000000b8/refund-request" \
-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",
"reason_code": "Added at the front desk"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/customer/orders/{orderId}/refund-request", {
params: { path: { orderId: "b80cf509-0000-4000-8000-d0c5000000b8" } },
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
reason_code: "Added at the front desk"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.requestOrderRefund(
path: .init(orderId: "b80cf509-0000-4000-8000-d0c5000000b8"),
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
reasonCode: "Added at the front desk"
))
).ok.body.json
print(response){
"request": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"status": "<status>",
"reason_code": "Added at the front desk",
"created_at": "<created_at>"
}
}Powers the one-tap "Order again" card. Returns the single newest paid order for the signed-in customer at one store, trimmed to order_number + order_type + collection_mode + bare item lines. The client re-prices and re-validates each line against the CURRENT menu, so no stale prices are ever returned.
Store whose most-recent paid order to seed a reorder from.
The newest paid order trimmed to what a reorder needs; null when there is none.
'buy_now' | 'collect'
'now' | 'scheduled' | null
curl -G "https://www.membber.com/api/v1/customer/orders/recent" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
--data-urlencode "store_id=6659c139-0000-4000-8000-d0c500000066"import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.GET("/api/v1/customer/orders/recent", {
params: { query: { store_id: "6659c139-0000-4000-8000-d0c500000066" } },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.getRecentStoreOrder(
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066")
).ok.body.json
print(response){
"order": {
"order_number": "<order_number>",
"order_type": "<order_type>",
"collection_mode": "<collection_mode>",
"items": [
{
"menu_item_id": "ca65a6e7-0000-4000-8000-d0c5000000ca",
"name": "Example name",
"quantity": 1
}
]
}
}The customer profile for one store: the store-scoped member_profiles row (health, address, ID verification fields) merged with the name and email from the customers table, so the settings screen can render one unified form. If no profile row exists yet, an empty shell is returned carrying the customer id, store id, name and email. The customer is always resolved from the session, never a client-supplied id.
Store whose profile to read (the profile is store-scoped).
member_profiles row id, or an empty string when no profile exists yet.
The signed-in customer id.
Store the profile belongs to.
Customer display name from the customers table.
Customer email from the customers table.
curl -G "https://www.membber.com/api/v1/customer/profile" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
--data-urlencode "store_id=6659c139-0000-4000-8000-d0c500000066"import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.GET("/api/v1/customer/profile", {
params: { query: { store_id: "6659c139-0000-4000-8000-d0c500000066" } },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.getCustomerProfile(
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066")
).ok.body.json
print(response){
"profile": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"customer_id": "96607d1c-0000-4000-8000-d0c500000096",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"name": "Example name",
"email": "alex@example.com",
"profile_image_url": "https://example.com/image.jpg",
"gender": "<gender>",
"date_of_birth": "<date_of_birth>",
"address_line_1": "<address_line_1>",
"address_line_2": "<address_line_2>",
"city": "<city>",
"postcode": "EXAMPLE10",
"medical_notes": "Added at the front desk",
"id_document_url": "https://example.com/image.jpg",
"id_verification_status": "<id_verification_status>",
"id_verified_at": "<id_verified_at>",
"id_verified_by": "<id_verified_by>",
"id_rejection_reason": "Added at the front desk",
"created_at": "<created_at>",
"updated_at": "<updated_at>"
}
}The customer app auto-save for the settings form: upserts the store-scoped member_profiles row (health, address, gender) and updates name/email on the customers table, then recomputes onboarding progress (non-fatal). Returns the merged profile view (identical shape to getCustomerProfile). Idempotent: the member_profiles upsert is keyed on (customer_id, store_id) and the customers update is a plain set, so a retried identical request converges to the same state. The customer is always resolved from the session, never a client-supplied id.
Store whose profile to write (the profile is store-scoped).
Customer display name (persisted on the customers table).
Customer email (lower-cased + trimmed server-side; customers table).
Gender display string; normalized to the DB value server-side.
ISO date; empty string is coerced to null.
member_profiles row id.
The signed-in customer id.
Store the profile belongs to.
Customer display name from the customers table.
Customer email from the customers table.
curl -X POST "https://www.membber.com/api/v1/customer/profile" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"store_id": "6659c139-0000-4000-8000-d0c500000066"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/customer/profile", {
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.upsertCustomerProfile(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066"
))
).ok.body.json
print(response){
"profile": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"customer_id": "96607d1c-0000-4000-8000-d0c500000096",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"name": "Example name",
"email": "alex@example.com",
"profile_image_url": "https://example.com/image.jpg",
"gender": "<gender>",
"date_of_birth": "<date_of_birth>",
"address_line_1": "<address_line_1>",
"address_line_2": "<address_line_2>",
"city": "<city>",
"postcode": "EXAMPLE10",
"medical_notes": "Added at the front desk",
"id_document_url": "https://example.com/image.jpg",
"id_verification_status": "<id_verification_status>",
"id_verified_at": "<id_verified_at>",
"id_verified_by": "<id_verified_by>",
"id_rejection_reason": "Added at the front desk",
"created_at": "<created_at>",
"updated_at": "<updated_at>"
}
}Booking pre-flight check for the signed-in customer at a given store: whether they satisfy the store-configured requirements (health questionnaire, waivers, emergency contact), the list of any outstanding requirement keys, and the per-requirement status. Each requirement is only enforced when the store enables it. The member is resolved from the session; the store is context, never identity.
Store whose booking requirements to check readiness against (context, not identity).
True when every store-required item is satisfied (missing is empty).
The requirement keys still outstanding (e.g. 'health_questionnaire', 'waiver', 'emergency_contact').
Whether a valid, unexpired health questionnaire is on file (true when not required by the store).
Whether all active waivers are signed (true when not required by the store).
Whether at least one emergency contact is on file (true when not required by the store).
curl -G "https://www.membber.com/api/v1/customer/readiness" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
--data-urlencode "store_id=6659c139-0000-4000-8000-d0c500000066"import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.GET("/api/v1/customer/readiness", {
params: { query: { store_id: "6659c139-0000-4000-8000-d0c500000066" } },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.getCustomerReadiness(
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066")
).ok.body.json
print(response){
"ready": true,
"missing": [
"<missing>"
],
"healthQuestionnaireValid": true,
"waiversComplete": true,
"emergencyContactSet": true
}