9 operations. Every schema and example on this page is generated from the platform contract.
Merchant lists their store memberships for the Members screen: membership rows with embedded plan and customer, the filtered total count, and a whole-roster status rollup (active / frozen / past_due / total) on the first unsearched page. Supports status filter, server-side name/email/phone search, an exact per-customer lookup, and pagination.
Store whose memberships to list (also authorises the merchant).
Filter to a single membership status (e.g. active, frozen, past_due).
Exact per-customer lookup, returns all of this customer's membership rows, unbounded by the page window.
Server-side name/email/phone match across the whole roster.
Page size (default 50).
Page offset (default 0).
curl -G "https://www.membber.com/api/v1/memberships" \
-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/memberships", {
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.listMemberships(
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066")
).ok.body.json
print(response){
"memberships": [
{
"id": "00000d1b-0000-4000-8000-d0c500000000",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"customer_id": "96607d1c-0000-4000-8000-d0c500000096",
"plan_id": "e28fa9f1-0000-4000-8000-d0c5000000e2",
"status": "<status>",
"created_at": "<created_at>"
}
],
"total": 1,
"statusCounts": {
"active": 1,
"frozen": 1,
"past_due": 1,
"total": 1
}
}Merchant staff enrols a customer into a gym membership plan: creates the membership and its Stripe subscription (or one-time PaymentIntent), gated by the can_use_member_billing entitlement and refused on a store that no longer serves new obligations. Money-moving. Returns the full membership row (with embedded plan + customer) for an immediate optimistic roster insert.
Store enrolling the member (also authorises the merchant).
Customer to enrol into the plan.
The gym membership plan to enrol the customer into.
Optional free-text staff note stored on the membership.
The enrolled membership row (with embedded plan + customer when the detail read-back succeeds).
curl -X POST "https://www.membber.com/api/v1/memberships" \
-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",
"customer_id": "96607d1c-0000-4000-8000-d0c500000096",
"plan_id": "e28fa9f1-0000-4000-8000-d0c5000000e2"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/memberships", {
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
customer_id: "96607d1c-0000-4000-8000-d0c500000096",
plan_id: "e28fa9f1-0000-4000-8000-d0c5000000e2"
},
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.enrollMember(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
customerId: "96607d1c-0000-4000-8000-d0c500000096",
planId: "e28fa9f1-0000-4000-8000-d0c5000000e2"
))
).ok.body.json
print(response){
"membership": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"customer_id": "96607d1c-0000-4000-8000-d0c500000096",
"plan_id": "e28fa9f1-0000-4000-8000-d0c5000000e2",
"status": "<status>",
"created_at": "<created_at>"
}
}Merchant staff cancels a gym membership: flips the Stripe subscription to cancel at period end and records the cancellation atomically (UPDATE membership + INSERT history in one transaction), then invalidates cache, pushes the wallet update, sends the scheduled-cancellation notification, and recomputes retention analytics. Gated by the can_use_member_billing entitlement + the can_process_payments staff permission. Money-adjacent (stops future billing). Idempotent: a membership already scheduled for cancellation returns already_scheduled=true.
Store the membership belongs to (also authorises the merchant).
Optional staff reason recorded on the cancellation history row (defaults to "Staff cancelled").
ISO timestamp the cancellation takes effect (end of current billing period).
True when a cancellation was already scheduled and this call was a no-op (idempotent).
curl -X POST "https://www.membber.com/api/v1/memberships/00000d1b-0000-4000-8000-d0c500000000/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"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/memberships/{id}/cancel", {
params: { path: { id: "00000d1b-0000-4000-8000-d0c500000000" } },
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.cancelMembership(
path: .init(id: "00000d1b-0000-4000-8000-d0c500000000"),
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066"
))
).ok.body.json
print(response){
"cancellation_effective_date": "<cancellation_effective_date>",
"already_scheduled": true
}Merchant staff freezes a gym membership: pauses the Stripe subscription collection (behavior: void) and records the freeze atomically (status flip + per-year freeze counters + history INSERT in one transaction), then recomputes retention analytics, refreshes the wallet pass, and pushes the customer a "membership frozen" notification. Gated by the can_use_member_billing entitlement + the can_process_payments staff permission. Enforces the freeze business rules (must be active, no pending cancellation, min-commitment met, max 2 freezes/year, min 7 days, max 90 days/year). Money-moving (changes what Stripe charges). Idempotent on (membership_id, freeze_end): a retry never double-bumps the per-year counters.
Store the membership belongs to (also authorises the merchant).
ISO timestamp the freeze ends (the resume date). Must be at least 7 days out.
Optional staff reason recorded on the freeze history row. Truncated to 500 chars server-side.
The date the freeze started (YYYY-MM-DD, server clock at the time of the call).
ISO timestamp the freeze ends (echoes the requested resume date).
Number of days the membership is frozen for.
curl -X POST "https://www.membber.com/api/v1/memberships/00000d1b-0000-4000-8000-d0c500000000/freeze" \
-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",
"freeze_end": "2026-07-01T09:00:00Z"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/memberships/{id}/freeze", {
params: { path: { id: "00000d1b-0000-4000-8000-d0c500000000" } },
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
freeze_end: "2026-07-01T09:00:00Z"
},
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.pauseMembership(
path: .init(id: "00000d1b-0000-4000-8000-d0c500000000"),
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
freezeEnd: "2026-07-01T09:00:00Z"
))
).ok.body.json
print(response){
"freeze_start": "<freeze_start>",
"freeze_end": "<freeze_end>",
"freeze_days": -9007199254740991
}Returns the member’s imported (transferred) membership awaiting setup: plan, preserved first-charge date, the £0-today terms and the consent hash the claim must echo. Null when the member has nothing to claim at this store.
curl -G "https://www.membber.com/api/v1/memberships/claim" \
-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/memberships/claim", {
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.getMembershipClaim(
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066")
).ok.body.json
print(response){
"claim": {
"membership_id": "bc83d224-0000-4000-8000-d0c5000000bc",
"plan_id": "e28fa9f1-0000-4000-8000-d0c5000000e2",
"plan_name": "<plan_name>",
"price_pence": 1500,
"billing_cycle": "<billing_cycle>",
"first_charge_date": "<first_charge_date>",
"first_charge_pence": 1500,
"requires_setup": true,
"consent_content_hash": "<consent_content_hash>",
"consent_snapshot": "<consent_snapshot>"
}
}Creates the Stripe subscription for an imported membership with the member’s preserved billing date honoured as a free period (nothing charged today), adopting the existing membership row. Returns the SetupIntent used to save the card. Idempotent: retrying resumes the same subscription.
Member accepted the claim billing terms.
Hash from the claim preview, must match the current terms.
Client-generated key; a retried claim resumes the same subscription.
curl -X POST "https://www.membber.com/api/v1/memberships/claim" \
-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",
"billing_consent_accepted": true,
"billing_consent_content_hash": "<billing_consent_content_hash>",
"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.POST("/api/v1/memberships/claim", {
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
billing_consent_accepted: true,
billing_consent_content_hash: "<billing_consent_content_hash>",
idempotency_key: "1f0e2d3c-4b5a-4678-9abc-def012345678"
},
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.claimImportedMembership(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
billingConsentAccepted: true,
billingConsentContentHash: "<billing_consent_content_hash>",
idempotencyKey: "1f0e2d3c-4b5a-4678-9abc-def012345678"
))
).ok.body.json
print(response){
"membership_id": "bc83d224-0000-4000-8000-d0c5000000bc",
"requires_setup": true,
"setup_intent_client_secret": "<setup_intent_client_secret>",
"ephemeral_key": "<ephemeral_key>",
"stripe_customer_id": "847b302a-0000-4000-8000-d0c500000084",
"connected_account_id": "231b6f63-0000-4000-8000-d0c500000023",
"first_charge_date": "<first_charge_date>",
"first_charge_pence": 1500
}Verifies the saved card on the claimed subscription and switches the membership on. Safe to retry; the billing webhooks provide the belt-and-braces path if this call is missed.
curl -X POST "https://www.membber.com/api/v1/memberships/claim/confirm" \
-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",
"membership_id": "bc83d224-0000-4000-8000-d0c5000000bc"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/memberships/claim/confirm", {
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
membership_id: "bc83d224-0000-4000-8000-d0c5000000bc"
},
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.confirmMembershipClaim(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
membershipId: "bc83d224-0000-4000-8000-d0c5000000bc"
))
).ok.body.json
print(response){
"status": "<status>"
}The customer's own membership status at a store: the active membership with classes remaining, or when they have no membership the available plans enriched with capacity (member count, is-full, spots remaining) plus any waitlist entries, the membership display config, and the self-service action config.
Store whose membership to read for the signed-in customer.
What this member pays today, in pence.
What they will pay after the change, in pence.
ISO currency of both amounts.
ISO timestamp the new price takes effect.
ISO timestamp the change was announced.
Whole days until the change lands. Never negative.
Optional note from the store owner.
curl -G "https://www.membber.com/api/v1/memberships/me" \
-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/memberships/me", {
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.getMyMembership(
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066")
).ok.body.json
print(response){
"has_membership": true,
"membership": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"customer_id": "96607d1c-0000-4000-8000-d0c500000096",
"plan_id": "e28fa9f1-0000-4000-8000-d0c5000000e2",
"member_number": 1,
"status": "<status>",
"status_changed_at": "<status_changed_at>",
"stripe_subscription_id": "d8bc238b-0000-4000-8000-d0c5000000d8",
"stripe_customer_id": "847b302a-0000-4000-8000-d0c500000084",
"current_period_start": "<current_period_start>",
"current_period_end": "<current_period_end>",
"classes_used_this_cycle": 1,
"cycle_reset_date": "<cycle_reset_date>",
"freeze_start": "<freeze_start>",
"freeze_end": "<freeze_end>",
"freeze_reason": "Added at the front desk",
"freezes_used_this_year": 1,
"total_freeze_days_year": 1,
"grace_period_start": "<grace_period_start>",
"grace_period_end": "<grace_period_end>",
"cancelled_at": "<cancelled_at>",
"cancellation_effective_date": "<cancellation_effective_date>",
"cancellation_reason": "Added at the front desk",
"min_commitment_end": "<min_commitment_end>",
"joined_at": "<joined_at>",
"enrolled_by": "<enrolled_by>",
"notes": "Added at the front desk",
"created_at": "<created_at>",
"updated_at": "<updated_at>",
"plan": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"name": "Example name",
"description": "Added at the front desk",
"plan_type": "<plan_type>",
"billing_cycle": "<billing_cycle>",
"price_pence": 1500,
"currency": "GBP",
"classes_per_cycle": 1,
"credit_expiry_days": 1,
"allowed_class_categories": [
"<allowed_class_categorie>"
],
"allowed_time_start": "<allowed_time_start>",
"allowed_time_end": "<allowed_time_end>",
"allowed_days": [
1
],
"min_commitment_months": 1,
"cancellation_notice_days": 1,
"joining_fee_pence": 1500,
"stripe_product_id": "25d87d9d-0000-4000-8000-d0c500000025",
"stripe_price_id": "588c2783-0000-4000-8000-d0c500000058",
"platform_stripe_price_id": "a1e99fb7-0000-4000-8000-d0c5000000a1",
"is_active": true,
"sort_order": 1,
"highlight_label": "<highlight_label>",
"max_members": 1,
"billing_cycle_type": "<billing_cycle_type>",
"billing_anchor_day": 1,
"billing_anchor_weekday": 1,
"billing_anchor_month": 1,
"proration_enabled": true,
"hero_image_url": "https://example.com/image.jpg",
"created_at": "<created_at>",
"updated_at": "<updated_at>"
}
},
"classes_remaining": 1,
"available_plans": [
{
"id": "00000d1b-0000-4000-8000-d0c500000000",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"name": "Example name",
"description": "Added at the front desk",
"plan_type": "<plan_type>",
"billing_cycle": "<billing_cycle>",
"price_pence": 1500,
"currency": "GBP",
"classes_per_cycle": 1,
"credit_expiry_days": 1,
"allowed_class_categories": [
"<allowed_class_categorie>"
],
"allowed_time_start": "<allowed_time_start>",
"allowed_time_end": "<allowed_time_end>",
"allowed_days": [
1
],
"min_commitment_months": 1,
"cancellation_notice_days": 1,
"joining_fee_pence": 1500,
"stripe_product_id": "25d87d9d-0000-4000-8000-d0c500000025",
"stripe_price_id": "588c2783-0000-4000-8000-d0c500000058",
"platform_stripe_price_id": "a1e99fb7-0000-4000-8000-d0c5000000a1",
"is_active": true,
"sort_order": 1,
"highlight_label": "<highlight_label>",
"max_members": 1,
"billing_cycle_type": "<billing_cycle_type>",
"billing_anchor_day": 1,
"billing_anchor_weekday": 1,
"billing_anchor_month": 1,
"proration_enabled": true,
"hero_image_url": "https://example.com/image.jpg",
"created_at": "<created_at>",
"updated_at": "<updated_at>",
"member_count": 1,
"is_full": true,
"spots_remaining": 1
}
],
"membership_display": {
"allow_self_service_signup": true,
"show_prices_publicly": true,
"default_picker_view_mode": "<default_picker_view_mode>"
},
"qualifies_for_gym_app_home": true,
"waitlist_entries": [
{
"id": "00000d1b-0000-4000-8000-d0c500000000",
"plan_id": "e28fa9f1-0000-4000-8000-d0c5000000e2",
"plan_name": "<plan_name>",
"position": 1,
"status": "<status>",
"joined_at": "<joined_at>"
}
],
"self_service_config": {
"allow_self_cancel": true,
"allow_self_cancel_reversal": true,
"allow_self_freeze": true,
"allow_self_resume": true,
"allow_plan_change": true
},
"pending_price_change": {
"plan_name": "<plan_name>",
"current_price_pence": 1500,
"new_price_pence": 1500,
"is_increase": true,
"currency": "GBP",
"billing_cycle": "<billing_cycle>",
"effective_date": "<effective_date>",
"announced_at": "<announced_at>",
"days_until_effective": 1,
"owner_message": "Added at the front desk"
}
}The signed-in customer cancels their OWN gym membership: gated by the gym allow_self_cancel toggle, it flips the Stripe subscription to cancel at period end and records the cancellation atomically (UPDATE membership + INSERT history in one transaction), then invalidates cache, pushes the wallet update, sends the scheduled-cancellation notification, and recomputes retention analytics. Money-adjacent (stops future billing, no partial refund). Idempotent: a membership already scheduled for cancellation returns already_scheduled=true.
Store whose membership the signed-in customer is cancelling (gates the gym's self-cancel toggle).
Optional member reason recorded on the cancellation history row (defaults to null). Truncated to 500 chars server-side.
ISO timestamp the cancellation takes effect (end of current billing period).
True when a cancellation was already scheduled and this call was a no-op (idempotent).
curl -X POST "https://www.membber.com/api/v1/memberships/me/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"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/memberships/me/cancel", {
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.cancelMyMembership(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066"
))
).ok.body.json
print(response){
"cancellation_effective_date": "<cancellation_effective_date>",
"already_scheduled": true
}