3 operations. Every schema and example on this page is generated from the platform contract.
Merchant reads the reward-goal wizard state: the current goal configuration (goal, max multi-stamp request, per-day change limit, template, timezone) and the single pending scheduled goal change for the store, if one exists. A goal change is grace-windowed: during the window the old goal applies, and the pending row records the old and new values plus when the change takes effect. Read-only.
Store whose pending goal change to read (also authorises the merchant).
The store's current goal configuration.
Current reward goal (stamps per voucher).
Current max stamps a customer may request in one scan.
Per-store cap on scheduled goal changes in a rolling 24h window.
Store template (e.g. cafe, gym), some templates gate goal changes.
Store timezone; scheduled changes take effect in this zone.
The single pending scheduled goal change for the store, if any.
When the change takes effect (ISO timestamp).
curl -G "https://www.membber.com/api/v1/goal-change" \
-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/goal-change", {
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.getGoalChange(
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066")
).ok.body.json
print(response){
"store": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"goal": 1,
"max_multi_stamp_request": 1,
"goal_change_rate_limit_per_day": 1,
"store_template": "<store_template>",
"timezone": "Europe/London"
},
"pending": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"old_goal": 1,
"new_goal": 1,
"old_max_multi": 1,
"new_max_multi": 1,
"effective_at": "<effective_at>",
"status": "<status>",
"created_at": "<created_at>"
}
}Merchant schedules a change to their reward goal + max multi-stamp request, effective after a delay. Inserted as a single pending row (last-write-wins supersede when replace_existing is true) and applied later by the per-minute cron, which issues vouchers atomically to any customer already at or above a lowered goal. The schedule_goal_change database function owns all validation (goal/max-multi/delay ranges, per-day rate limit, gym/inactive gating) and writes the admin audit row; its error codes map faithfully to the response envelope.
Store to schedule the change for (also authorises the merchant; identity is taken from the verified session, never this field).
Proposed new reward goal (stamps per voucher); the DB owns the valid range (3..15).
Proposed new max stamps a customer may request in one scan; the DB owns the valid range (1..30).
Grace window before the change takes effect; the DB owns the valid range (60s .. 31 days).
When true, supersede any existing pending change (last-write-wins); when false/omitted, an existing pending change rejects with PENDING_CHANGE_EXISTS.
Id of the newly created pending scheduled goal change.
When the change takes effect (ISO timestamp).
Id of the previous pending change that was superseded (null when there was none).
curl -X POST "https://www.membber.com/api/v1/goal-change" \
-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",
"new_goal": -9007199254740991,
"new_max_multi": -9007199254740991,
"delay_seconds": -9007199254740991
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/goal-change", {
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
new_goal: -9007199254740991,
new_max_multi: -9007199254740991,
delay_seconds: -9007199254740991
},
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.scheduleGoalChange(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
newGoal: -9007199254740991,
newMaxMulti: -9007199254740991,
delaySeconds: -9007199254740991
))
).ok.body.json
print(response){
"scheduled_change_id": "4f4706f8-0000-4000-8000-d0c50000004f",
"effective_at": "<effective_at>",
"superseded_id": "6ded1824-0000-4000-8000-d0c50000006d"
}Merchant confirm-step preview: for a proposed new goal, reports how many customers would earn a voucher immediately when the change applies and how many are mid-cycle. Read-only; the preview is computed by the get_goal_change_impact_preview database function, which also validates the goal range and returns a success/error body faithfully passed through.
Store to preview against (also authorises the merchant).
Proposed new reward goal; the DB owns the valid range (3..15).
Whether the preview computed successfully.
Error code when success is false (STORE_NOT_FOUND or GOAL_OUT_OF_RANGE).
The store's current goal.
The proposed new goal echoed back.
Customers who would earn a voucher immediately when the new goal applies.
Customers with unused stamps who would not immediately earn under the new goal.
curl -G "https://www.membber.com/api/v1/goal-change/preview" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
--data-urlencode "store_id=6659c139-0000-4000-8000-d0c500000066" \
--data-urlencode "new_goal=-9007199254740991"import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.GET("/api/v1/goal-change/preview", {
params: { query: { store_id: "6659c139-0000-4000-8000-d0c500000066", new_goal: "-9007199254740991" } },
});
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.getGoalChangePreview(
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066", newGoal: "-9007199254740991")
).ok.body.json
print(response){
"success": true,
"code": "EXAMPLE10",
"current_goal": 1,
"new_goal": 1,
"customers_earning_voucher": 1,
"customers_in_progress": 1
}