Loyalty

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

Read a customer's current loyalty state at a store.

Merchant reads a single customer’s driver-aware loyalty rollup at their store: stamps toward the next reward, active (redeemable) vouchers, order count + net spend, and (gyms) membership status.

auth merchant:readop getCustomerLoyaltyStaterate limitederrors

Parameters

store_idstring · uuidqueryrequired

Store whose loyalty state to read (also authorises the merchant).

customer_idstring · uuidqueryrequired

Customer to read the loyalty state for.

Response, 200

stamp_countnumberrequired
goalnumberrequired
reward_namestringrequired
loyalty_enabledbooleanrequired
active_vouchersarray of objectrequired
5 child fields
idstringrequired
codestring, nullablerequired
reward_titlestring, nullablerequired
voucher_typestring, nullablerequired
expires_atstring, nullablerequired
order_countnumberrequired
total_spent_pencenumberrequired
currencystringrequired
membershipobjectrequired
3 child fields
statusstring, nullablerequired
current_period_endstring, nullablerequired
plan_namestring, nullablerequired
phonestring, nullablerequired
emailstring, nullablerequired
last_visit_atstring, nullablerequired
curl -G "https://www.membber.com/api/v1/loyalty/customer-state" \
  -H "Authorization: Bearer $MEMBBER_TOKEN" \
  --data-urlencode "store_id=6659c139-0000-4000-8000-d0c500000066" \
  --data-urlencode "customer_id=96607d1c-0000-4000-8000-d0c500000096"
Response, 200
{
  "stamp_count": 1,
  "goal": 1,
  "reward_name": "<reward_name>",
  "loyalty_enabled": true,
  "active_vouchers": [
    {
      "id": "00000d1b-0000-4000-8000-d0c500000000",
      "code": "EXAMPLE10",
      "reward_title": "<reward_title>",
      "voucher_type": "<voucher_type>",
      "expires_at": "<expires_at>"
    }
  ],
  "order_count": 1,
  "total_spent_pence": 1500,
  "currency": "GBP",
  "membership": {
    "status": "<status>",
    "current_period_end": "<current_period_end>",
    "plan_name": "<plan_name>"
  },
  "phone": "+44 7700 900123",
  "email": "alex@example.com",
  "last_visit_at": "<last_visit_at>"
}

Cancel a pending multi-stamp freeze request.

The customer exits the freeze (cancel button, phone lock, backgrounded). Idempotent: a missing, already-resolved, or not-owned request all return cancelled=true (the not-owned case mirrors not-found to prevent request-id enumeration).

Request body

request_idstring · uuidrequired

The pending freeze request to cancel.

Response, 200

cancelledbooleanrequired
previous_statusstringoptional
curl -X POST "https://www.membber.com/api/v1/loyalty/multi-stamp/cancel" \
  -H "Authorization: Bearer $MEMBBER_TOKEN" \
  -H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
  -H "Content-Type: application/json" \
  -d '{
    "request_id": "023640cb-0000-4000-8000-d0c500000002"
  }'
Response, 200
{
  "cancelled": true,
  "previous_status": "<previous_status>"
}

Confirm a pending multi-stamp freeze with a staff auth card.

A staff auth card confirms a customer’s pending freeze; confirm_multi_stamp_request creates N stamps atomically and mints any voucher at goal. When the store enforces NTAG 424, verified auth-card proof is required, the tag counter is consumed ONLY after the pending request is resolved, so a no-op tap never burns it.

Request body

store_codestringrequired

Short store code from the staff auth-card URL (/redeem/{storeCode}/{secret}).

request_idstring · uuid, nullableoptional

The pending request to confirm; omit to confirm the customer’s latest in-window pending request.

staff_secretstring, nullableoptional

The auth card token_public_id (staff credential) when no staff JWT session is present.

staff_tokenstring, nullableoptional

Staff JWT session token. Alternative to the X-Staff-Token header; do NOT put it in Authorization (that carries the customer session).

nfc_picc_datastring, nullableoptional

NTAG 424 encrypted PICCData (required when the store enforces NFC).

nfc_cmacstring, nullableoptional

NTAG 424 AES-CMAC (required when the store enforces NFC).

nfc_key_versionstring, nullableoptional

NTAG 424 key version (required when the store enforces NFC).

Response, 200

actionstringrequired
stamps_creatednumberrequired
current_stampsnumberrequired
goalnumberrequired
voucher_earnedbooleanrequired
vouchers_earnednumberrequired
voucher_resultsarray of anyrequired
store_namestringrequired
store_logo_urlstring, nullablerequired
store_codestringrequired
primary_colorstring, nullablerequired
secondary_colorstring, nullablerequired
background_image_urlstring, nullablerequired
background_typestringrequired
background_colorstring, nullablerequired
stamp_visualstring, nullablerequired
store_templatestring, nullablerequired
idempotentbooleanrequired
curl -X POST "https://www.membber.com/api/v1/loyalty/multi-stamp/confirm" \
  -H "Authorization: Bearer $MEMBBER_TOKEN" \
  -H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
  -H "Content-Type: application/json" \
  -d '{
    "store_code": "EXAMPLE10"
  }'
Response, 200
{
  "action": "<action>",
  "stamps_created": 1,
  "current_stamps": 1,
  "goal": 1,
  "voucher_earned": true,
  "vouchers_earned": 1,
  "voucher_results": [
    "<voucher_result>"
  ],
  "store_name": "<store_name>",
  "store_logo_url": "https://example.com/image.jpg",
  "store_code": "EXAMPLE10",
  "primary_color": "<primary_color>",
  "secondary_color": "<secondary_color>",
  "background_image_url": "https://example.com/image.jpg",
  "background_type": "<background_type>",
  "background_color": "<background_color>",
  "stamp_visual": "<stamp_visual>",
  "store_template": "<store_template>",
  "idempotent": true
}

Request several loyalty stamps at once (creates a 60s freeze).

A customer requests N stamps in one go; a pending multi_stamp_requests row is created with a 60s TTL for a staff auth card to confirm. Kill-switch, entitlement, dark-store freeze, and the per-customer monthly ceiling are all enforced. Idempotent-ish: an in-window pending request 409s rather than double-creating.

Request body

store_idstringrequired

Store UUID or short store code to request the stamps at.

requested_stampsintegerrequired

How many stamps to request in one freeze (1..min(max_multi_stamp_request, goal)).

Response, 200

request_idstringrequired
requested_stampsnumberrequired
expires_atstringrequired
expires_in_secondsnumberrequired
curl -X POST "https://www.membber.com/api/v1/loyalty/multi-stamp/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",
    "requested_stamps": 1
  }'
Response, 200
{
  "request_id": "023640cb-0000-4000-8000-d0c500000002",
  "requested_stamps": 1,
  "expires_at": "<expires_at>",
  "expires_in_seconds": 1
}

Check a customer’s current multi-stamp freeze state.

Returns the pending freeze request (within its grace window), else the last request confirmed in the past 60s. An unknown/inactive store returns has_pending_request=false (not an error) so the client can route to normal redemption.

Parameters

store_codestring, nullablequeryoptional

Short store code (provide this or store_id).

store_idstring · uuid, nullablequeryoptional

Store UUID (provide this or store_code).

Response, 200

has_pending_requestbooleanrequired
requestobjectoptional
5 child fields
idstringrequired
requested_stampsnumberrequired
statusstringrequired
expires_atstringrequired
remaining_secondsnumberrequired
last_confirmedobjectoptional
4 child fields
idstringrequired
requested_stampsnumberrequired
statusstringrequired
confirmed_atstringrequired
curl -G "https://www.membber.com/api/v1/loyalty/multi-stamp/status" \
  -H "Authorization: Bearer $MEMBBER_TOKEN"
Response, 200
{
  "has_pending_request": true,
  "request": {
    "id": "00000d1b-0000-4000-8000-d0c500000000",
    "requested_stamps": 1,
    "status": "<status>",
    "expires_at": "<expires_at>",
    "remaining_seconds": 1
  },
  "last_confirmed": {
    "id": "00000d1b-0000-4000-8000-d0c500000000",
    "requested_stamps": 1,
    "status": "<status>",
    "confirmed_at": "<confirmed_at>"
  }
}

List a store’s stamps awaiting review (or any status).

Merchant reads the pending-stamp queue (default status=pending), the stamps a customer collected that need staff approve/reject. Newest first, capped at 100, each with the collecting customer embedded.

Parameters

store_idstring · uuidqueryrequired

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

statusstringqueryrequired

Stamp status to filter by (default 'pending').

Response, 200

stampsarray of objectrequired
10 child fields
idstringrequired
store_idstringrequired
customer_idstring, nullablerequired
statusstringrequired
sourcestring, nullablerequired
approval_reasonstring, nullablerequired
approvedboolean, nullablerequired
created_atstring, nullablerequired
approved_atstring, nullablerequired
customerobjectrequired
8 child fields
idstringrequired
namestring, nullablerequired
emailstring, nullablerequired
phonestring, nullablerequired
apple_user_idstring, nullablerequired
last_seenstring, nullablerequired
created_atstring, nullablerequired
updated_atstring, nullablerequired
curl -G "https://www.membber.com/api/v1/loyalty/pending-stamps" \
  -H "Authorization: Bearer $MEMBBER_TOKEN" \
  --data-urlencode "store_id=6659c139-0000-4000-8000-d0c500000066" \
  --data-urlencode "status=<status>"
Response, 200
{
  "stamps": [
    {
      "id": "00000d1b-0000-4000-8000-d0c500000000",
      "store_id": "6659c139-0000-4000-8000-d0c500000066",
      "customer_id": "96607d1c-0000-4000-8000-d0c500000096",
      "status": "<status>",
      "source": "<source>",
      "approval_reason": "Added at the front desk",
      "approved": true,
      "created_at": "<created_at>",
      "approved_at": "<approved_at>",
      "customer": {
        "id": "00000d1b-0000-4000-8000-d0c500000000",
        "name": "Example name",
        "email": "alex@example.com",
        "phone": "+44 7700 900123",
        "apple_user_id": "f3321e2a-0000-4000-8000-d0c5000000f3",
        "last_seen": "<last_seen>",
        "created_at": "<created_at>",
        "updated_at": "<updated_at>"
      }
    }
  ]
}

Approve (transactional) or reject a pending stamp.

Merchant approves a pending stamp via the atomic approve_stamp_transaction RPC (voucher issuance + stamp consumption stay consistent) and pushes the customer’s Apple Wallet pass, or rejects it (status flip). The stamp must belong to the authorised store (IDOR guard). Idempotent at the RPC layer.

Request body

store_idstring · uuidrequired

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

stamp_idstring · uuidrequired

The pending stamp to approve or reject.

actionenumrequired

approve = award (transactional voucher issuance); reject = decline.

approvereject

Response, 200

actionenumrequired
approvereject
resultobjectrequired

The approve_stamp_transaction RPC result (approve only; null on reject).

13 child fields
successbooleanoptional
errorstringoptional
limit_reachedbooleanoptional
monthly_limitnumberoptional
stamps_usednumberoptional
plan_keystringoptional
stamp_countnumberoptional
voucher_createdbooleanoptional
voucher_codestringoptional
voucher_idstringoptional
stamp_idstringoptional
messagestringoptional
already_approvedbooleanoptional
curl -X POST "https://www.membber.com/api/v1/loyalty/pending-stamps/resolve" \
  -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",
    "stamp_id": "4e347317-0000-4000-8000-d0c50000004e",
    "action": "approve"
  }'
Response, 200
{
  "action": "approve",
  "result": {
    "success": true,
    "error": "<error>",
    "limit_reached": true,
    "monthly_limit": 1,
    "stamps_used": 1,
    "plan_key": "<plan_key>",
    "stamp_count": 1,
    "voucher_created": true,
    "voucher_code": "EXAMPLE10",
    "voucher_id": "89b53c4c-0000-4000-8000-d0c500000089",
    "stamp_id": "4e347317-0000-4000-8000-d0c50000004e",
    "message": "Added at the front desk",
    "already_approved": true
  }
}

Redeem one of a customer's active vouchers.

Merchant honours a "reward ready" voucher from the customer hub (the Bearer equivalent of a staff QR redeem): race-safe status flip (no double-redeem), wallet-pass void + push.

Request body

store_idstring · uuidrequired

Store redeeming the reward (also authorises the merchant).

customer_idstring · uuidrequired

Customer whose voucher is being redeemed.

voucher_idstring · uuidrequired

The active voucher to redeem.

Response, 200

voucher_idstringrequired
redemption_codestringrequired
reward_titlestring, nullablerequired
curl -X POST "https://www.membber.com/api/v1/loyalty/redemptions" \
  -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",
    "voucher_id": "89b53c4c-0000-4000-8000-d0c500000089"
  }'
Response, 200
{
  "voucher_id": "89b53c4c-0000-4000-8000-d0c500000089",
  "redemption_code": "EXAMPLE10",
  "reward_title": "<reward_title>"
}

Generate a one-time stamp code for an NFC tap or QR scan.

Mints a short-lived one-time code the customer shows at the counter. When the tap carries NTAG 424 proof the tag counter is verified and consumed (proving physical presence); a replayed tap (page refresh) re-serves the code the first tap minted rather than erroring. Authenticated callers get a 60s per-customer dedupe; unauthenticated (cookie-less passkey) callers mint a code with no customer bound yet.

auth none, publicop generateStampCodeidempotent retryrate limitederrors

Request body

store_idstring · uuidrequired

Store the tap happened at.

ttl_secondsintegerrequired

How long the code stays valid (default 60s).

device_infostring, nullableoptional

Opaque device fingerprint, also scopes the NFC replay-recovery lookup.

location_infostring, nullableoptional

Opaque location hint recorded for security monitoring.

sourcestring, nullableoptional

How the code was invoked: 'nfc'/'n', 'qr'/'q', else 'web'. Full NFC proof forces 'nfc'.

nfc_picc_datastring, nullableoptional

NTAG 424 encrypted PICCData (all three nfc_* fields together = a proof).

nfc_cmacstring, nullableoptional

NTAG 424 AES-CMAC.

nfc_key_versionstring, nullableoptional

NTAG 424 key version.

Response, 200

codestringrequired
full_messagestringrequired
store_codestringrequired
store_namestringrequired
expires_atstringrequired
expires_in_secondsnumberrequired
presence_verifiedbooleanoptional
deduplicatedbooleanoptional
messagestringrequired
curl -X POST "https://www.membber.com/api/v1/loyalty/stamp-codes" \
  -H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
  -H "Content-Type: application/json" \
  -d '{
    "store_id": "6659c139-0000-4000-8000-d0c500000066",
    "ttl_seconds": 1
  }'
Response, 200
{
  "code": "EXAMPLE10",
  "full_message": "Added at the front desk",
  "store_code": "EXAMPLE10",
  "store_name": "<store_name>",
  "expires_at": "<expires_at>",
  "expires_in_seconds": 1,
  "presence_verified": true,
  "deduplicated": true,
  "message": "Added at the front desk"
}

Grant one approved loyalty stamp to a customer.

Merchant grants one stamp from the customer hub (the Bearer + customerId equivalent of a staff scan): daily + monthly limits enforced, atomic voucher issuance at goal, wallet push. Idempotent via idempotency_key.

Request body

store_idstring · uuidrequired

Store granting the stamp (also authorises the merchant).

customer_idstring · uuidrequired

Customer to grant a stamp to.

idempotency_keystring, nullableoptional

Body idempotency key (order-level dedup).

Response, 200

stamp_idstring, nullablerequired
current_stampsnumberrequired
goalnumberrequired
voucher_earnedbooleanrequired
voucher_codestring, nullablerequired
idempotentbooleanrequired
curl -X POST "https://www.membber.com/api/v1/loyalty/stamps" \
  -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"
  }'
Response, 200
{
  "stamp_id": "4e347317-0000-4000-8000-d0c50000004e",
  "current_stamps": 1,
  "goal": 1,
  "voucher_earned": true,
  "voucher_code": "EXAMPLE10",
  "idempotent": true
}

Approve or reject a stamp (the atomic ApprovalService path).

Merchant approves/rejects a stamp via the ApprovalService atomic transaction, the lock-screen "Approve" action the Business app fires. On approve it pushes the customer’s Apple Wallet pass AND notifies the merchant; reject notifies the merchant. The stamp must belong to the authorised store (IDOR guard).

Request body

store_idstring · uuidrequired

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

stamp_idstring · uuidrequired

The pending stamp to approve or reject.

actionenumrequired

approve = award; reject = decline.

approvereject

Response, 200

actionenumrequired
approvereject
messagestringrequired
stamp_countnumber, nullablerequired
voucher_createdbooleanrequired
voucher_codestring, nullablerequired
curl -X POST "https://www.membber.com/api/v1/loyalty/stamps/review" \
  -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",
    "stamp_id": "4e347317-0000-4000-8000-d0c50000004e",
    "action": "approve"
  }'
Response, 200
{
  "action": "approve",
  "message": "Added at the front desk",
  "stamp_count": 1,
  "voucher_created": true,
  "voucher_code": "EXAMPLE10"
}

Flush stamps the customer app recorded while offline.

Submits up to 20 queued offline tap receipts. Each is deduped by receipt_id (so re-syncing is safe), rejected if older than 7 days or future-dated, checked against the store’s daily limit ON THE TAP DATE, and written with the ORIGINAL tap timestamp. NTAG 424 proof (when carried) decides approved-vs-pending; the Apple Wallet pass is pushed once per store whose approved count changed.

Request body

stampsarray of objectrequired

The queued offline receipts (1..20 per batch).

7 child fields
receipt_idstring, nullableoptional

Client-generated id, the dedupe key, so a re-sync never double-stamps. Missing ⇒ that receipt alone fails with missing_fields.

store_codestring, nullableoptional

Store UUID or short store code the tap happened at. Missing ⇒ that receipt alone fails with missing_fields.

tap_timestampstring, nullableoptional

ISO8601 time of the ORIGINAL tap, the stamp is written with this created_at, and the daily limit is evaluated on this date.

sourcestring, nullableoptional

Where the tap came from (defaults to 'offline_nfc' when absent).

nfc_picc_datastring, nullableoptional

NTAG 424 encrypted PICCData (all three nfc_* fields together = a proof).

nfc_cmacstring, nullableoptional

NTAG 424 AES-CMAC.

nfc_key_versionstring, nullableoptional

NTAG 424 key version.

Response, 200

resultsarray of objectrequired
11 child fields
receipt_idstringrequired
successbooleanrequired
stamp_idstringoptional
stamp_statusenumoptional
pendingapproved
current_stampsnumberoptional
goalnumberoptional
voucher_earnedbooleanoptional
voucher_codestringoptional
duplicatebooleanoptional
errorstringoptional
messagestringoptional
curl -X POST "https://www.membber.com/api/v1/loyalty/stamps/sync-offline" \
  -H "Authorization: Bearer $MEMBBER_TOKEN" \
  -H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
  -H "Content-Type: application/json" \
  -d '{
    "stamps": [
      {
        "receipt_id": "0c2b3482-0000-4000-8000-d0c50000000c",
        "store_code": "EXAMPLE10",
        "tap_timestamp": "<tap_timestamp>",
        "source": "<source>",
        "nfc_picc_data": "<nfc_picc_data>",
        "nfc_cmac": "<nfc_cmac>",
        "nfc_key_version": "<nfc_key_version>"
      }
    ]
  }'
Response, 200
{
  "results": [
    {
      "receipt_id": "0c2b3482-0000-4000-8000-d0c50000000c",
      "success": true,
      "stamp_id": "4e347317-0000-4000-8000-d0c50000004e",
      "stamp_status": "pending",
      "current_stamps": 1,
      "goal": 1,
      "voucher_earned": true,
      "voucher_code": "EXAMPLE10",
      "duplicate": true,
      "error": "<error>",
      "message": "Added at the front desk"
    }
  ]
}

Undo a just-granted merchant-hub stamp (the TLC-bar undo affordance).

Reverses a stamp only if it belongs to this customer + store, was hub-granted, and is still unused. Idempotent: undoing an already-removed stamp is a no-op success.

Request body

store_idstring · uuidrequired

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

customer_idstring · uuidrequired

Customer the stamp belongs to.

stamp_idstring · uuidrequired

The just-granted merchant-hub stamp to undo.

Response, 200

undonebooleanrequired
current_stampsnumberrequired
curl -X POST "https://www.membber.com/api/v1/loyalty/stamps/undo" \
  -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",
    "stamp_id": "4e347317-0000-4000-8000-d0c50000004e"
  }'
Response, 200
{
  "undone": true,
  "current_stamps": 1
}
WhatsApp
Book a Call
Start Free