13 operations. Every schema and example on this page is generated from the platform contract.
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.
Store whose loyalty state to read (also authorises the merchant).
Customer to read the loyalty state for.
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"import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.GET("/api/v1/loyalty/customer-state", {
params: { query: { store_id: "6659c139-0000-4000-8000-d0c500000066", customer_id: "96607d1c-0000-4000-8000-d0c500000096" } },
});
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.getCustomerLoyaltyState(
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066", customerId: "96607d1c-0000-4000-8000-d0c500000096")
).ok.body.json
print(response){
"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>"
}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).
The pending freeze request to cancel.
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"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/loyalty/multi-stamp/cancel", {
body: {
request_id: "023640cb-0000-4000-8000-d0c500000002"
},
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.cancelMultiStamp(
body: .json(.init(
requestId: "023640cb-0000-4000-8000-d0c500000002"
))
).ok.body.json
print(response){
"cancelled": true,
"previous_status": "<previous_status>"
}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.
Short store code from the staff auth-card URL (/redeem/{storeCode}/{secret}).
The pending request to confirm; omit to confirm the customer’s latest in-window pending request.
The auth card token_public_id (staff credential) when no staff JWT session is present.
Staff JWT session token. Alternative to the X-Staff-Token header; do NOT put it in Authorization (that carries the customer session).
NTAG 424 encrypted PICCData (required when the store enforces NFC).
NTAG 424 AES-CMAC (required when the store enforces NFC).
NTAG 424 key version (required when the store enforces NFC).
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"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/loyalty/multi-stamp/confirm", {
body: {
store_code: "EXAMPLE10"
},
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.confirmMultiStamp(
body: .json(.init(
storeCode: "EXAMPLE10"
))
).ok.body.json
print(response){
"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
}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.
Store UUID or short store code to request the stamps at.
How many stamps to request in one freeze (1..min(max_multi_stamp_request, goal)).
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
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/loyalty/multi-stamp/request", {
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
requested_stamps: 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.requestMultiStamp(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
requestedStamps: 1
))
).ok.body.json
print(response){
"request_id": "023640cb-0000-4000-8000-d0c500000002",
"requested_stamps": 1,
"expires_at": "<expires_at>",
"expires_in_seconds": 1
}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.
Short store code (provide this or store_id).
Store UUID (provide this or store_code).
curl -G "https://www.membber.com/api/v1/loyalty/multi-stamp/status" \
-H "Authorization: Bearer $MEMBBER_TOKEN"import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.GET("/api/v1/loyalty/multi-stamp/status");
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.getMultiStampStatus().ok.body.json
print(response){
"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>"
}
}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.
Store whose stamps to list (also authorises the merchant).
Stamp status to filter by (default 'pending').
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>"import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.GET("/api/v1/loyalty/pending-stamps", {
params: { query: { store_id: "6659c139-0000-4000-8000-d0c500000066", status: "<status>" } },
});
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.listPendingStamps(
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066", status: "<status>")
).ok.body.json
print(response){
"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>"
}
}
]
}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.
Store the stamp belongs to (also authorises the merchant).
The pending stamp to approve or reject.
approve = award (transactional voucher issuance); reject = decline.
approverejectapproverejectThe approve_stamp_transaction RPC result (approve only; null on reject).
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"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/loyalty/pending-stamps/resolve", {
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
stamp_id: "4e347317-0000-4000-8000-d0c50000004e",
action: "approve"
},
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.resolvePendingStamp(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
stampId: "4e347317-0000-4000-8000-d0c50000004e",
action: .approve
))
).ok.body.json
print(response){
"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
}
}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.
Store redeeming the reward (also authorises the merchant).
Customer whose voucher is being redeemed.
The active voucher to redeem.
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"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/loyalty/redemptions", {
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
customer_id: "96607d1c-0000-4000-8000-d0c500000096",
voucher_id: "89b53c4c-0000-4000-8000-d0c500000089"
},
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.redeemReward(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
customerId: "96607d1c-0000-4000-8000-d0c500000096",
voucherId: "89b53c4c-0000-4000-8000-d0c500000089"
))
).ok.body.json
print(response){
"voucher_id": "89b53c4c-0000-4000-8000-d0c500000089",
"redemption_code": "EXAMPLE10",
"reward_title": "<reward_title>"
}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.
Store the tap happened at.
How long the code stays valid (default 60s).
Opaque device fingerprint, also scopes the NFC replay-recovery lookup.
Opaque location hint recorded for security monitoring.
How the code was invoked: 'nfc'/'n', 'qr'/'q', else 'web'. Full NFC proof forces 'nfc'.
NTAG 424 encrypted PICCData (all three nfc_* fields together = a proof).
NTAG 424 AES-CMAC.
NTAG 424 key version.
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
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/loyalty/stamp-codes", {
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
ttl_seconds: 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.generateStampCode(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
ttlSeconds: 1
))
).ok.body.json
print(response){
"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"
}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.
Store granting the stamp (also authorises the merchant).
Customer to grant a stamp to.
Body idempotency key (order-level dedup).
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"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/loyalty/stamps", {
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
customer_id: "96607d1c-0000-4000-8000-d0c500000096"
},
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.grantStamp(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
customerId: "96607d1c-0000-4000-8000-d0c500000096"
))
).ok.body.json
print(response){
"stamp_id": "4e347317-0000-4000-8000-d0c50000004e",
"current_stamps": 1,
"goal": 1,
"voucher_earned": true,
"voucher_code": "EXAMPLE10",
"idempotent": true
}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).
Store the stamp belongs to (also authorises the merchant).
The pending stamp to approve or reject.
approve = award; reject = decline.
approverejectapproverejectcurl -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"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/loyalty/stamps/review", {
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
stamp_id: "4e347317-0000-4000-8000-d0c50000004e",
action: "approve"
},
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.reviewStamp(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
stampId: "4e347317-0000-4000-8000-d0c50000004e",
action: .approve
))
).ok.body.json
print(response){
"action": "approve",
"message": "Added at the front desk",
"stamp_count": 1,
"voucher_created": true,
"voucher_code": "EXAMPLE10"
}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.
The queued offline receipts (1..20 per batch).
Client-generated id, the dedupe key, so a re-sync never double-stamps. Missing ⇒ that receipt alone fails with missing_fields.
Store UUID or short store code the tap happened at. Missing ⇒ that receipt alone fails with missing_fields.
ISO8601 time of the ORIGINAL tap, the stamp is written with this created_at, and the daily limit is evaluated on this date.
Where the tap came from (defaults to 'offline_nfc' when absent).
NTAG 424 encrypted PICCData (all three nfc_* fields together = a proof).
NTAG 424 AES-CMAC.
NTAG 424 key version.
pendingapprovedcurl -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>"
}
]
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/loyalty/stamps/sync-offline", {
body: {
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>"
}
]
},
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.syncOfflineStamps(
body: .json(.init(
stamps: [.init(
receiptId: "0c2b3482-0000-4000-8000-d0c50000000c",
storeCode: "EXAMPLE10",
tapTimestamp: "<tap_timestamp>",
source: "<source>",
nfcPiccData: "<nfc_picc_data>",
nfcCmac: "<nfc_cmac>",
nfcKeyVersion: "<nfc_key_version>"
)]
))
).ok.body.json
print(response){
"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"
}
]
}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.
Store the stamp belongs to (also authorises the merchant).
Customer the stamp belongs to.
The just-granted merchant-hub stamp to undo.
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"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/loyalty/stamps/undo", {
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
customer_id: "96607d1c-0000-4000-8000-d0c500000096",
stamp_id: "4e347317-0000-4000-8000-d0c50000004e"
},
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.undoStamp(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
customerId: "96607d1c-0000-4000-8000-d0c500000096",
stampId: "4e347317-0000-4000-8000-d0c50000004e"
))
).ok.body.json
print(response){
"undone": true,
"current_stamps": 1
}