API referenceMemberships

Memberships

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

List a store's memberships.

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.

Parameters

store_idstring · uuidqueryrequired

Store whose memberships to list (also authorises the merchant).

statusstringqueryoptional

Filter to a single membership status (e.g. active, frozen, past_due).

customer_idstring · uuidqueryoptional

Exact per-customer lookup, returns all of this customer's membership rows, unbounded by the page window.

searchstringqueryoptional

Server-side name/email/phone match across the whole roster.

limitintegerqueryoptional

Page size (default 50).

offsetintegerqueryoptional

Page offset (default 0).

Response, 200

membershipsarray of objectrequired
6 child fields
idstringrequired
store_idstringrequired
customer_idstring, nullableoptional
plan_idstring, nullableoptional
statusstring, nullableoptional
created_atstring, nullableoptional
totalnumberrequired
statusCountsobjectrequired
4 child fields
activenumberrequired
frozennumberrequired
past_duenumberrequired
totalnumberrequired
curl -G "https://www.membber.com/api/v1/memberships" \
  -H "Authorization: Bearer $MEMBBER_TOKEN" \
  --data-urlencode "store_id=6659c139-0000-4000-8000-d0c500000066"
Response, 200
{
  "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
  }
}

Enrol a customer into a membership plan.

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.

Request body

store_idstring · uuidrequired

Store enrolling the member (also authorises the merchant).

customer_idstring · uuidrequired

Customer to enrol into the plan.

plan_idstring · uuidrequired

The gym membership plan to enrol the customer into.

notesstring, nullableoptional

Optional free-text staff note stored on the membership.

Response, 200

membershipobjectrequired

The enrolled membership row (with embedded plan + customer when the detail read-back succeeds).

6 child fields
idstringrequired
store_idstringrequired
customer_idstring, nullableoptional
plan_idstring, nullableoptional
statusstring, nullableoptional
created_atstring, nullableoptional
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"
  }'
Response, 200
{
  "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>"
  }
}

Ask a past-due member to add or update their payment card.

The reason-correct recovery action on the Members › Money needs-attention list: emails (and best-effort pushes) a past-due member to add/update their card in the app. `ask_to_update` (no working card) uses firm copy; `remind` (decline while Stripe still retries) uses a softer nudge. Store-scoped and business-authed; gated by the can_use_member_billing entitlement + the can_process_payments staff permission. Sends no money. Cooldown-protected: a repeat within the window is a no-op returning already_asked_recently=true so the member is never blasted. The lifecycle-notification row is the append-only audit.

Parameters

idstringpathrequired

Request body

store_idstring · uuidrequired

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

modeenumrequired

ask_to_update = no working card on file (firm "add a card" copy); remind = a decline while Stripe is still auto-retrying (softer "check your card" nudge). Mirrors the row's suggested_action; a "retry" row must use retry-payment, not this.

ask_to_updateremind

Response, 200

sentbooleanrequired

True when at least one channel (email/push) delivered the ask just now.

already_asked_recentlybooleanrequired

True when the member was asked within the cooldown window, so nothing was sent this time.

channelsarray of enumrequired

Which channels delivered the ask this call (empty when already_asked_recently, or the member is unreachable).

emailpush
asked_atstring, nullablerequired

ISO timestamp of the ask that is in effect (this send, or the prior one when in cooldown); null when unreachable.

next_allowed_atstring, nullablerequired

ISO timestamp the merchant can ask this member again; null when unreachable (may ask again now).

curl -X POST "https://www.membber.com/api/v1/memberships/00000d1b-0000-4000-8000-d0c500000000/ask-card-update" \
  -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",
    "mode": "ask_to_update"
  }'
Response, 200
{
  "sent": true,
  "already_asked_recently": true,
  "channels": [
    "email"
  ],
  "asked_at": "<asked_at>",
  "next_allowed_at": "<next_allowed_at>"
}

Cancel a membership at the end of its current billing period.

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.

Parameters

idstringpathrequired

Request body

store_idstring · uuidrequired

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

reasonstring, nullableoptional

Optional staff reason recorded on the cancellation history row (defaults to "Staff cancelled").

Response, 200

cancellation_effective_datestring, nullablerequired

ISO timestamp the cancellation takes effect (end of current billing period).

already_scheduledbooleanrequired

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"
  }'
Response, 200
{
  "cancellation_effective_date": "<cancellation_effective_date>",
  "already_scheduled": true
}

Freeze a membership: pause its Stripe billing for a set period.

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.

Parameters

idstringpathrequired

Request body

store_idstring · uuidrequired

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

freeze_endstring · date-timerequired

ISO timestamp the freeze ends (the resume date). Must be at least 7 days out.

reasonstring, nullableoptional

Optional staff reason recorded on the freeze history row. Truncated to 500 chars server-side.

Response, 200

freeze_startstringrequired

The date the freeze started (YYYY-MM-DD, server clock at the time of the call).

freeze_endstringrequired

ISO timestamp the freeze ends (echoes the requested resume date).

freeze_daysintegerrequired

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"
  }'
Response, 200
{
  "freeze_start": "<freeze_start>",
  "freeze_end": "<freeze_end>",
  "freeze_days": -9007199254740991
}

Charge a member a one-off fee on their saved card.

Merchant charges a member a single fee (PT session, product, damaged kit, a manually-settled no-show): an off-session destination charge on their saved card carrying the standard uniform platform fee (1.5% margin + Stripe-cost recovery). Requires a note; the member is notified. Store-scoped and business-authed; gated by the can_use_member_billing entitlement + the can_process_payments staff permission. Idempotent via the caller key (no double-charge). A non-ok result (no card, cancelled, no account, SCA/decline) is a successful body with ok=false, not an HTTP error.

Parameters

idstringpathrequired

Request body

store_idstring · uuidrequired

Store the membership belongs to (also authorises the merchant). Never trusted for identity beyond authorisation.

amount_penceintegerrequired

The fee to charge, in pence (min 50, max 100000 = £1,000 fat-finger guard).

notestringrequired

REQUIRED reason for the fee, recorded on the ledger and shown to the member (e.g. "PT session", "damaged kit").

idempotency_keystringrequired

Caller-generated token, stable across retries of ONE submission → the Stripe idempotency key (prevents a double-charge).

Response, 200

okbooleanrequired

Whether the fee was charged. false = a friendly, non-error outcome the merchant UI shows.

payment_intent_idstringoptional

Stripe PaymentIntent id of the successful charge.

amount_pencenumberoptional

Amount charged, in pence.

statusenumoptional

Why the charge did not complete (drives the merchant-readable reason).

no_cardno_accountcancellednot_foundinvalidfailed
failure_codestringoptional

Underlying Stripe/guard failure code (e.g. authentication_required, store_dark).

errorstringoptional

Human-readable failure detail.

curl -X POST "https://www.membber.com/api/v1/memberships/00000d1b-0000-4000-8000-d0c500000000/one-off-fee" \
  -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",
    "amount_pence": 1500,
    "note": "Added at the front desk",
    "idempotency_key": "1f0e2d3c-4b5a-4678-9abc-def012345678"
  }'
Response, 200
{
  "ok": true,
  "payment_intent_id": "0b6642a5-0000-4000-8000-d0c50000000b",
  "amount_pence": 1500,
  "status": "no_card",
  "failure_code": "EXAMPLE10",
  "error": "<error>"
}

Preview a transferred-membership claim.

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.

Parameters

store_idstring · uuidqueryrequired

Response, 200

claimobjectrequired
10 child fields
membership_idstringrequired
plan_idstringrequired
plan_namestringrequired
price_pencenumberrequired
billing_cyclestringrequired
first_charge_datestring, nullablerequired
first_charge_pencenumberrequired
requires_setupbooleanrequired
consent_content_hashstring, nullablerequired
consent_snapshotstring, nullablerequired
curl -G "https://www.membber.com/api/v1/memberships/claim" \
  -H "Authorization: Bearer $MEMBBER_TOKEN" \
  --data-urlencode "store_id=6659c139-0000-4000-8000-d0c500000066"
Response, 200
{
  "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>"
  }
}

Claim a transferred membership (£0 today).

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.

Request body

store_idstring · uuidrequired
billing_consent_acceptedbooleanrequired

Member accepted the claim billing terms.

billing_consent_content_hashstring, nullablerequired

Hash from the claim preview, must match the current terms.

idempotency_keystringrequired

Client-generated key; a retried claim resumes the same subscription.

Response, 200

membership_idstringrequired
requires_setupbooleanrequired
setup_intent_client_secretstring, nullablerequired
ephemeral_keystring, nullablerequired
stripe_customer_idstring, nullablerequired
connected_account_idstring, nullablerequired
first_charge_datestring, nullablerequired
first_charge_pencenumberrequired
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"
  }'
Response, 200
{
  "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
}

Finish a transferred-membership claim.

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.

Request body

store_idstring · uuidrequired
membership_idstring · uuidrequired

Response, 200

statusstringrequired
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"
  }'
Response, 200
{
  "status": "<status>"
}

Get the signed-in customer's membership at a store.

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.

Parameters

store_idstring · uuidqueryrequired

Store whose membership to read for the signed-in customer.

Response, 200

has_membershipbooleanrequired
membershipobjectrequired
30 child fields
idstringrequired
store_idstringrequired
customer_idstringrequired
plan_idstringrequired
member_numbernumber, nullablerequired
statusstringrequired
status_changed_atstringrequired
stripe_subscription_idstring, nullablerequired
stripe_customer_idstring, nullablerequired
current_period_startstring, nullablerequired
current_period_endstring, nullablerequired
classes_used_this_cyclenumberrequired
cycle_reset_datestring, nullablerequired
freeze_startstring, nullablerequired
freeze_endstring, nullablerequired
freeze_reasonstring, nullablerequired
freezes_used_this_yearnumberrequired
total_freeze_days_yearnumberrequired
grace_period_startstring, nullablerequired
grace_period_endstring, nullablerequired
cancelled_atstring, nullablerequired
cancellation_effective_datestring, nullablerequired
cancellation_reasonstring, nullablerequired
min_commitment_endstring, nullablerequired
joined_atstringrequired
enrolled_bystring, nullablerequired
notesstring, nullablerequired
created_atstringrequired
updated_atstringrequired
planobjectrequired
32 child fields
idstringrequired
store_idstringrequired
namestringrequired
descriptionstring, nullablerequired
plan_typestringrequired
billing_cyclestringrequired
price_pencenumber, nullablerequired
currencystringrequired
classes_per_cyclenumber, nullablerequired
credit_expiry_daysnumber, nullablerequired
allowed_class_categoriesarray of stringrequired
allowed_time_startstring, nullablerequired
allowed_time_endstring, nullablerequired
allowed_daysarray of numberrequired
min_commitment_monthsnumberrequired
cancellation_notice_daysnumberrequired
joining_fee_pencenumberrequired
stripe_product_idstring, nullablerequired
stripe_price_idstring, nullablerequired
platform_stripe_price_idstring, nullablerequired
is_activebooleanrequired
sort_ordernumber, nullablerequired
highlight_labelstring, nullablerequired
max_membersnumber, nullablerequired
billing_cycle_typestringrequired
billing_anchor_daynumber, nullablerequired
billing_anchor_weekdaynumber, nullablerequired
billing_anchor_monthnumber, nullablerequired
proration_enabledbooleanrequired
hero_image_urlstring, nullablerequired
created_atstringrequired
updated_atstringrequired
classes_remainingnumber, nullablerequired
available_plansarray of objectrequired
35 child fields
idstringrequired
store_idstringrequired
namestringrequired
descriptionstring, nullablerequired
plan_typestringrequired
billing_cyclestringrequired
price_pencenumber, nullablerequired
currencystringrequired
classes_per_cyclenumber, nullablerequired
credit_expiry_daysnumber, nullablerequired
allowed_class_categoriesarray of stringrequired
allowed_time_startstring, nullablerequired
allowed_time_endstring, nullablerequired
allowed_daysarray of numberrequired
min_commitment_monthsnumberrequired
cancellation_notice_daysnumberrequired
joining_fee_pencenumberrequired
stripe_product_idstring, nullablerequired
stripe_price_idstring, nullablerequired
platform_stripe_price_idstring, nullablerequired
is_activebooleanrequired
sort_ordernumber, nullablerequired
highlight_labelstring, nullablerequired
max_membersnumber, nullablerequired
billing_cycle_typestringrequired
billing_anchor_daynumber, nullablerequired
billing_anchor_weekdaynumber, nullablerequired
billing_anchor_monthnumber, nullablerequired
proration_enabledbooleanrequired
hero_image_urlstring, nullablerequired
created_atstringrequired
updated_atstringrequired
member_countnumberoptional
is_fullbooleanoptional
spots_remainingnumber, nullableoptional
membership_displayobjectrequired
3 child fields
allow_self_service_signupbooleanrequired
show_prices_publiclybooleanrequired
default_picker_view_modestringrequired
qualifies_for_gym_app_homebooleanrequired
waitlist_entriesarray of objectoptional
6 child fields
idstringrequired
plan_idstringrequired
plan_namestringrequired
positionnumberrequired
statusstringrequired
joined_atstringrequired
self_service_configobjectoptional
5 child fields
allow_self_cancelbooleanrequired
allow_self_cancel_reversalbooleanrequired
allow_self_freezebooleanrequired
allow_self_resumebooleanrequired
allow_plan_changebooleanrequired
pending_price_changeobjectoptional
10 child fields
plan_namestringrequired
current_price_pencenumberrequired

What this member pays today, in pence.

new_price_pencenumberrequired

What they will pay after the change, in pence.

is_increasebooleanrequired
currencystringrequired

ISO currency of both amounts.

billing_cyclestringrequired
effective_datestringrequired

ISO timestamp the new price takes effect.

announced_atstring, nullablerequired

ISO timestamp the change was announced.

days_until_effectivenumberrequired

Whole days until the change lands. Never negative.

owner_messagestring, nullablerequired

Optional note from the store owner.

refundobjectoptional

The member's own view of a refund on this membership. Never includes an amount.

4 child fields
enabledbooleanrequired

Does this gym take refund requests in the app at all? Per-store, default OFF.

can_requestbooleanrequired

May she open a NEW request right now. False when the gym has it off, the window has closed, the membership has ended, or one is already open. Every clause mirrors a refusal the POST handler enforces, so the screen never offers what the write path will reject.

window_closes_atstring, nullablerequired

When the window shuts. Null when unknowable or already shut.

requestobjectrequired

The live or most recent refund request on this membership.

8 child fields
idstringrequired
stageenumrequired

Where her request has got to, in her words rather than the table's. "problem" is a refund that failed and is deliberately not dressed up as still coming, that is how someone waits a week before ringing.

askedapprovedon_its_wayreturneddeclinedproblem
reason_codestringrequired

Why she said she was asking.

messagestring, nullablerequired

Her own words, echoed back so the screen can show what she sent.

decision_notestring, nullablerequired

The owner's reason, and only ever once he has actually decided. Never a number.

created_atstringrequired
decided_atstring, nullablerequired
completed_atstring, nullablerequired
curl -G "https://www.membber.com/api/v1/memberships/me" \
  -H "Authorization: Bearer $MEMBBER_TOKEN" \
  --data-urlencode "store_id=6659c139-0000-4000-8000-d0c500000066"
Response, 200
{
  "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"
  },
  "refund": {
    "enabled": true,
    "can_request": true,
    "window_closes_at": "<window_closes_at>",
    "request": {
      "id": "00000d1b-0000-4000-8000-d0c500000000",
      "stage": "asked",
      "reason_code": "Added at the front desk",
      "message": "Added at the front desk",
      "decision_note": "Added at the front desk",
      "created_at": "<created_at>",
      "decided_at": "<decided_at>",
      "completed_at": "<completed_at>"
    }
  }
}

Cancel the signed-in customer's own membership at the end of the current billing period.

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.

Request body

store_idstring · uuidrequired

Store whose membership the signed-in customer is cancelling (gates the gym's self-cancel toggle).

reasonstring, nullableoptional

Optional member reason recorded on the cancellation history row (defaults to null). Truncated to 500 chars server-side.

Response, 200

cancellation_effective_datestring, nullablerequired

ISO timestamp the cancellation takes effect (end of current billing period).

already_scheduledbooleanrequired

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"
  }'
Response, 200
{
  "cancellation_effective_date": "<cancellation_effective_date>",
  "already_scheduled": true
}

Get a store's member-money summary.

The gym Members › Money dashboard hero: a store's money at a glance, collected this month (net of refunds), money at risk, active/late/failed member counts, and the past-due needs-attention list (failed ranked above late). Store-scoped and business-authed; read-only; every value is real store data and anonymized/archived customers are excluded via the reportable-customers guard.

auth merchant:readop getMembershipMoneySummaryrate limitederrors

Parameters

store_idstring · uuidqueryrequired

Store whose money summary to read (also authorises the merchant).

Response, 200

currencystringrequired

ISO currency of every amount (e.g. "gbp").

collectedobjectrequired

This-month collected money (single source: subscription_payment − subscription_refund).

2 child fields
month_to_date_net_pencenumberrequired

Collected this calendar month to date, NET of refunds, in pence.

has_any_chargesbooleanrequired

Whether the store has EVER taken a membership charge, false ⇒ render a designed blank, never £0.00.

at_riskobjectrequired

Money the store may not collect (past-due members).

2 child fields
countnumberrequired

Number of past-due members.

pencenumberrequired

Sum of past-due members monthly price, an honest proxy for money at risk.

countsobjectrequired

Headline member-money counts.

3 child fields
activenumberrequired

Active paying members.

latenumberrequired

Past-due members Stripe is still auto-retrying.

failednumberrequired

Past-due members whose retries are spent.

needs_attentionarray of objectrequired

Past-due members, failed ranked above late (act-now before wait-and-see).

10 child fields
membership_idstringrequired

The gym_memberships row id.

customer_idstringrequired

The member (customer) id, open their page to act.

namestringrequired

Member display name.

plan_namestringrequired

The plan they are on.

monthly_pencenumberrequired

The member own monthly price in pence (what an overdue cycle is worth).

stateenumrequired

late = Stripe is still auto-retrying (grace window open); failed = retries spent, act now.

latefailed
sincestring, nullablerequired

ISO timestamp the member entered trouble, or null.

reasonenumrequired

Why the payment is failing, from the member default card: no_card / card_expired (dead) / declined.

no_cardcard_expireddeclined
suggested_actionenumrequired

The reason-correct recovery action (never retry a dead card): ask_to_update (no/dead/expired card), remind (declined while still auto-retrying), retry (declined with retries spent).

ask_to_updateremindretry
ask_cooldown_untilstring, nullablerequired

ISO timestamp: when set and in the future, this member was asked to update their card within the cooldown window, so the UI shows an "asked, resend in N" state instead of a fresh ask button. Null ⇒ never asked or the cooldown has passed (ask freely).

curl -G "https://www.membber.com/api/v1/memberships/money-summary" \
  -H "Authorization: Bearer $MEMBBER_TOKEN" \
  --data-urlencode "store_id=6659c139-0000-4000-8000-d0c500000066"
Response, 200
{
  "currency": "GBP",
  "collected": {
    "month_to_date_net_pence": 1500,
    "has_any_charges": true
  },
  "at_risk": {
    "count": 1,
    "pence": 1
  },
  "counts": {
    "active": 1,
    "late": 1,
    "failed": 1
  },
  "needs_attention": [
    {
      "membership_id": "bc83d224-0000-4000-8000-d0c5000000bc",
      "customer_id": "96607d1c-0000-4000-8000-d0c500000096",
      "name": "Example name",
      "plan_name": "<plan_name>",
      "monthly_pence": 1500,
      "state": "late",
      "since": "<since>",
      "reason": "no_card",
      "suggested_action": "ask_to_update",
      "ask_cooldown_until": "<ask_cooldown_until>"
    }
  ]
}

List every charge the store has taken, with the totals for the window.

The Taken half of the gym Money face: a store-wide, newest-first list of real charges, filterable by kind, date window and member, and searchable by description or Stripe object id. Every figure is summed from real ledger rows; an empty window returns real zeros so the client can render a designed blank. Gated on the payments permission because it exposes per-member charge amounts store-wide.

Parameters

store_idstring · uuidqueryrequired

Store whose charges to read (also authorises the merchant).

fromstringqueryoptional

Inclusive ISO lower bound on charge time. Omit for all time.

tostringqueryoptional

Exclusive ISO upper bound on charge time. Omit for up-to-now.

kindsstringqueryoptional

Comma-separated kinds to include (membership,fee,extra,credit,refund). An unknown kind is REJECTED rather than ignored: silently widening a filter would show more money than the merchant asked to see and make the header disagree with the filter chips on screen.

customer_idstringqueryoptional

Restrict to one member.

qstringqueryoptional

Free text matched against the charge DESCRIPTION and the Stripe object id. Deliberately NOT the member name: names live on the customers table, and filtering a joined column would silently drop rows rather than search them. Name search resolves to a customer_id first.

limitintegerqueryoptional

Page size (default 100, max 500).

Response, 200

chargesarray of objectrequired
11 child fields
idstringrequired

The membership_ledger_entries row id.

created_atstringrequired

ISO timestamp the charge was taken; the client groups by day.

event_typestringrequired

The raw ledger event type.

kindenumrequired

How a charge is filed on the board, driven by the ledger event_type and NOT by the sign of the amount (a downgrade credit and a refund are both negative but are not the same thing to a merchant): membership = subscription payment / upgrade proration; fee = a penalty such as a no-show or late cancel; extra = day passes / PT / anything else taken; credit = goodwill given to the member (credit applied, freeze credit, downgrade credit) which is NOT a refund of a charge; refund = money that went back (subscription or drop-in refund, or a chargeback). There is deliberately no `pack`: no ledger event_type distinguishes a class-pack purchase, and an accepted-but-unmappable filter would return an EMPTY board with zero totals, reading as "you took nothing" rather than "that filter is unavailable".

membershipfeeextracreditrefund
amount_pencenumberrequired

Signed pence exactly as recorded. Refunds are negative.

currencystringrequired
descriptionstring, nullablerequired

What it was for, as recorded on the ledger row.

stripe_object_idstring, nullablerequired

The Stripe invoice / charge / refund id. This is the handle a per-period refund targets (target_object_id on the refund contract), so a row can be refunded straight from the board.

customer_idstring, nullablerequired
membership_idstring, nullablerequired

The membership this charge belongs to. Required to refund FROM a row: the refund endpoint is membership-scoped, so without it a row is readable but not actionable.

customer_namestring, nullablerequired

The member name, or NULL when no reportable customer stands behind the row (deleted/anonymized, or a charge with no customer). Never a placeholder: inventing a name over a real charge would be a fabricated fact about money.

totalsobjectrequired
8 child fields
taken_pencenumberrequired

Sum of money IN across the whole filtered window (not just the page).

refunded_pencenumberrequired

Sum of money OUT as a POSITIVE figure, never netted off the take.

refund_countnumberrequired

How many refunds in the window, the deck "2 refunded this month".

charge_countnumberrequired
credited_pencenumberrequired

Sum of goodwill/credit rows as a POSITIVE figure. Neither taken nor refunded.

currencystringrequired
by_kindarray of objectrequired

Money-in breakdown driving the header, biggest first.

3 child fields
kindenumrequired

How a charge is filed on the board, driven by the ledger event_type and NOT by the sign of the amount (a downgrade credit and a refund are both negative but are not the same thing to a merchant): membership = subscription payment / upgrade proration; fee = a penalty such as a no-show or late cancel; extra = day passes / PT / anything else taken; credit = goodwill given to the member (credit applied, freeze credit, downgrade credit) which is NOT a refund of a charge; refund = money that went back (subscription or drop-in refund, or a chargeback). There is deliberately no `pack`: no ledger event_type distinguishes a class-pack purchase, and an accepted-but-unmappable filter would return an EMPTY board with zero totals, reading as "you took nothing" rather than "that filter is unavailable".

membershipfeeextracreditrefund
countnumberrequired
amount_pencenumberrequired
truncatedbooleanrequired

TRUE when the window held more rows than the totals read could cover, so every figure here is a FLOOR rather than the whole truth and the client must say so. A silently-capped total is the worst failure on this screen: the header is what a merchant reconciles against their bank.

has_morebooleanrequired

More rows exist in the window than this page holds.

curl -G "https://www.membber.com/api/v1/memberships/taken" \
  -H "Authorization: Bearer $MEMBBER_TOKEN" \
  --data-urlencode "store_id=6659c139-0000-4000-8000-d0c500000066"
Response, 200
{
  "charges": [
    {
      "id": "00000d1b-0000-4000-8000-d0c500000000",
      "created_at": "<created_at>",
      "event_type": "<event_type>",
      "kind": "membership",
      "amount_pence": 1500,
      "currency": "GBP",
      "description": "Added at the front desk",
      "stripe_object_id": "dedeb0a9-0000-4000-8000-d0c5000000de",
      "customer_id": "96607d1c-0000-4000-8000-d0c500000096",
      "membership_id": "bc83d224-0000-4000-8000-d0c5000000bc",
      "customer_name": "Alex Example"
    }
  ],
  "totals": {
    "taken_pence": 1500,
    "refunded_pence": 1500,
    "refund_count": 1,
    "charge_count": 1,
    "credited_pence": 1500,
    "currency": "GBP",
    "by_kind": [
      {
        "kind": "membership",
        "count": 1,
        "amount_pence": 1500
      }
    ],
    "truncated": true
  },
  "has_more": true
}
WhatsApp
Book a Call
Start Free