10 operations. Every schema and example on this page is generated from the platform contract.
Merchant read of a store's configuration: VAT registration + rate (surfaced on payout breakdowns) and the retention Reply-To inbox address stamped on outgoing lifecycle email. Consolidates the legacy settings/vat and settings/reply-to reads into one configuration read.
Store whose settings to fetch (also authorises the merchant).
Whether the store is VAT-registered.
VAT rate percent (0-100) when registered, else null.
Customer-facing Reply-To address for retention email, or null when unset.
curl -G "https://www.membber.com/api/v1/settings" \
-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/settings", {
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.getStoreSettings(
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066")
).ok.body.json
print(response){
"vat_registered": true,
"vat_rate_percent": 1,
"reply_to_email": "alex@example.com"
}Returns the store’s `gym_class_settings`. A store that has never saved these gets the platform DEFAULTS (7 days advance booking, 12:00 Europe/London release, 2h cancellation window, waitlist on with max 10, no venue cap, 2h reminder) rather than an error, so the settings screen always has something real to render.
Store whose class rules these are (also authorises the merchant).
The store’s `gym_class_settings` row: { store_id, advance_booking_days, daily_release_time, release_timezone, cancellation_window_hours, waitlist_enabled, max_waitlist_size, venue_capacity, reminder_hours_before, updated_at }.
curl -G "https://www.membber.com/api/v1/settings/classes" \
-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/settings/classes", {
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.getClassSettings(
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066")
).ok.body.json
print(response){
"settings": {}
}Upserts `gym_class_settings`. ONLY the keys you send are written, so nudging one stepper can never silently reset another rule; when no row exists yet the insert is seeded with the platform defaults first. Every change writes a plan-activity row describing what moved, categorised as a cancellation-rule change when the cancellation window moved (it changes whether a member’s cancel is free) and a booking-rule change otherwise. Idempotency `rpc`: the upsert is keyed on store_id, so a retry converges on the same row.
Store whose class rules these are (also authorises the merchant).
How many days ahead members may book, 1-90.
When each day’s classes open for booking, "HH:MM" or "HH:MM:SS".
IANA timezone the release time is interpreted in (e.g. Europe/London).
How long before a class a member may cancel free of penalty, 0-168 hours.
Whether full classes accept a waitlist.
Cap on waitlist length.
Total people the venue holds across overlapping classes. null = no cap.
How many hours before a class reminders send.
The full settings row after the update.
curl -X PUT "https://www.membber.com/api/v1/settings/classes" \
-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.PUT("/api/v1/settings/classes", {
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.updateClassSettings(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066"
))
).ok.body.json
print(response){
"settings": {}
}Store whose receipt switches to read (also authorises the merchant).
Email the member a receipt when they pay for a single drop-in class.
Email the member a receipt when they buy a class pack.
Email the member a receipt when a missed-class fee is charged. Defaults FALSE (founder decision D1): the fee is visible in the app and on the bank statement either way, so the email mainly invites an argument.
curl -G "https://www.membber.com/api/v1/settings/receipts" \
-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/settings/receipts", {
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.getStoreReceiptSettings(
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066")
).ok.body.json
print(response){
"success": true,
"receipt_dropin_enabled": true,
"receipt_pack_enabled": true,
"receipt_noshow_fee_enabled": true
}Store whose receipt switches to update (also authorises the merchant).
Email the member a receipt when they pay for a single drop-in class.
Email the member a receipt when they buy a class pack.
Email the member a receipt when a missed-class fee is charged. Defaults FALSE (founder decision D1): the fee is visible in the app and on the bank statement either way, so the email mainly invites an argument.
curl -X PUT "https://www.membber.com/api/v1/settings/receipts" \
-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.PUT("/api/v1/settings/receipts", {
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.updateStoreReceiptSettings(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066"
))
).ok.body.json
print(response){
"success": true,
"receipt_dropin_enabled": true,
"receipt_pack_enabled": true,
"receipt_noshow_fee_enabled": true
}Merchant sets (or clears) the customer-facing Reply-To address stamped on outgoing retention / lifecycle emails. Blank clears it. Single-row atomic UPDATE + an admin_audit_log row on every save. Faithful migration of the legacy PUT settings/reply-to. No money is touched.
Store whose reply-to inbox to update (also authorises the merchant).
Reply-to inbox email. null / omitted / blank clears it (stored NULL). Non-blank values are trimmed + lowercased before validation.
The persisted reply-to inbox (null when cleared).
curl -X PUT "https://www.membber.com/api/v1/settings/reply-to" \
-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.PUT("/api/v1/settings/reply-to", {
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.updateStoreReplyToEmail(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066"
))
).ok.body.json
print(response){
"reply_to_email": "alex@example.com"
}Gives the local part back to the pool (someone else can claim it). Idempotent: releasing a store that holds nothing is a successful no-op. An audit_logs row records every release. No money is touched.
Store releasing its sender identity (also authorises the merchant).
The claimed local part (before the @), or null when the store sends from the shared address.
Stored display-name override. null means the store name is used at send time (current behaviour for every store).
The campaign From address this store sends from right now: the claimed local part applied to the platform campaign domain, or the shared campaign address when nothing is claimed. Preview this verbatim; never assemble an address client-side.
curl -X DELETE "https://www.membber.com/api/v1/settings/sender-identity?store_id=6659c139-0000-4000-8000-d0c500000066" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678"import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.DELETE("/api/v1/settings/sender-identity", {
params: { query: { store_id: "6659c139-0000-4000-8000-d0c500000066" } },
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.deleteStoreSenderIdentity(
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066")
).ok.body.json
print(response){
"success": true,
"sender_local_part": "<sender_local_part>",
"sender_display_name": "<sender_display_name>",
"from_address": "<from_address>"
}Store whose sender identity to read (also authorises the merchant).
The claimed local part (before the @), or null when the store sends from the shared address.
Stored display-name override. null means the store name is used at send time (current behaviour for every store).
The campaign From address this store sends from right now: the claimed local part applied to the platform campaign domain, or the shared campaign address when nothing is claimed. Preview this verbatim; never assemble an address client-side.
curl -G "https://www.membber.com/api/v1/settings/sender-identity" \
-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/settings/sender-identity", {
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.getStoreSenderIdentity(
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066")
).ok.body.json
print(response){
"success": true,
"sender_local_part": "<sender_local_part>",
"sender_display_name": "<sender_display_name>",
"from_address": "<from_address>"
}First-come-first-served claim of a local part on the shared campaign domain. The server normalises the requested name, enforces the denylist, and answers a collision with IDENTITY_TAKEN plus availability-checked suggestions. Atomic single-row upsert + an audit_logs row on every claim. No money is touched.
Store claiming the identity (also authorises the merchant).
Requested sender name (the part before the @). The server normalises it exactly like the store slug (lowercase, spaces to hyphens, apostrophes dropped, & becomes "and") and then requires 2-63 chars of [a-z0-9.-] starting with a letter or number. Rejections: INVALID_FORMAT, RESERVED_WORD, IDENTITY_TAKEN (409, details.suggestions).
Sender display name. Omit to keep the stored value; null or blank clears it, and the store name is then used at send time.
The claimed local part (before the @), or null when the store sends from the shared address.
Stored display-name override. null means the store name is used at send time (current behaviour for every store).
The campaign From address this store sends from right now: the claimed local part applied to the platform campaign domain, or the shared campaign address when nothing is claimed. Preview this verbatim; never assemble an address client-side.
curl -X PUT "https://www.membber.com/api/v1/settings/sender-identity" \
-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",
"local_part": "<local_part>"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.PUT("/api/v1/settings/sender-identity", {
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
local_part: "<local_part>"
},
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.updateStoreSenderIdentity(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
localPart: "<local_part>"
))
).ok.body.json
print(response){
"success": true,
"sender_local_part": "<sender_local_part>",
"sender_display_name": "<sender_display_name>",
"from_address": "<from_address>"
}Merchant sets whether the store is VAT-registered and, if so, the rate (0-100). Surfaced as the "Of which VAT (X%)" line on payout breakdowns; the payouts breakdown cache is busted on save. Config-write gated (can_manage_config). Faithful migration of the legacy PUT settings/vat.
Store whose VAT settings to update (also authorises the merchant).
Whether the store is VAT-registered.
VAT rate percent (0-100) when registered; ignored (stored null) when not registered.
The persisted VAT-registered flag.
The persisted VAT rate percent (null when not registered).
curl -X PUT "https://www.membber.com/api/v1/settings/vat" \
-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",
"vat_registered": true,
"vat_rate_percent": 1
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.PUT("/api/v1/settings/vat", {
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
vat_registered: true,
vat_rate_percent: 1
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.updateStoreVatSettings(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
vatRegistered: true,
vatRatePercent: 1
))
).ok.body.json
print(response){
"vat_registered": true,
"vat_rate_percent": 1
}