LoyaltyCustomers
Loyalty
Customers
Add, link, search, read and update customers.
You name a customer with your own id, externalCustomerId. Adding or enrolling a customer answers with references only, nothing you sent is echoed back. To reach someone the shop already has, link your id to their member code instead of creating a second customer.
A phone number is never returned. You see maskedPhone (•••• 4821) and the six-character memberCode. You may send a phone to enroll or to look someone up by the whole number. Enrolling never turns on marketing and never undoes an opt-out.
Paths are relative to https://app.revisit.cards/v1/developer.
Endpoints
- GETSearch and page the business's customers
/customers - POSTAdd or update a customer by your own customer id
/customers - POSTLink your customer id to a customer the business already has
/customers/links - GETCount customers
/customers/count - GETRead a customer's card history
/customers/{id}/history - GETRead one customer
/customers/{id} - PATCHChange a customer's details
/customers/{id} - DELETEPermanently erase a customer
/customers/{id} - PUTWithdraw a customer's marketing consent
/customers/{id}/marketing-consent
Search and page the business's customers
/customersScopecustomers:read
Requires customers:read. Newest customers first, in pages of at most 50 (limit, default 25) with offset up to 10,000; hasMore says whether another page exists. Filters: q (a name fragment, or the six-character member code), phone (the customer's WHOLE number in any form POST /customers accepts; never a partial one: part of a number finds nobody), externalCustomerId (your own id), programId and branchId (customers seen at that branch). Unknown parameters are 422. Whole-number lookups are limited to 20 a minute per key (429). A customer is visible to a key through the programs, and for a branch-limited key the branches, it may use; the memberships listed are only those programs'. A program or branch the key cannot use is 404. Phones come back only as maskedPhone (last four digits); the email is never returned. Erased customers and the owner's test pass are never listed.
Query
qstring- 1 to 64 characters.
phonestring- The whole phone number to find.
externalCustomerIdstringprogramIduuidbranchIduuidsegmentenum- Only the customers one of the app's buckets names, by the rules the dashboard counts with: ready (a reward can be claimed now), one-away (stamp cards: next stamp fills the card), lapsed (joined over 30 days ago, no visit in 30), new (first membership in the last 30 Dhaka days), champions (5 or more visit days in 30). Read card by card over the cards the key may use; a segment that spans more than 24 card and branch reads or 20,000 customers is 422, narrow it with programId or branchId. One of
ready,one-away,lapsed,new,champions. limitinteger- 1 to 50. Default
25. offsetinteger- 0 to 10000. Default
0.
Returns 200
itemsobject[]Requireditems[].customerIdstringRequireditems[].externalCustomerIdstring or nullRequired- The id this application linked for the customer; null when it has not named them.
items[].memberCodestringRequired- Six characters staff and the customer see, e.g. K7M4QX.
items[].namestringRequireditems[].maskedPhonestringRequired- Last four digits only, e.g. "•••• 4821"; empty when the customer has no phone.
items[].localestring or nullRequireditems[].createdAtdate-timeRequireditems[].membershipsobject[]Required- Only the memberships in programs this key may use.
items[].memberships[].membershipIdstringRequireditems[].memberships[].programIdstringRequireditems[].memberships[].cardTypestringRequireditems[].memberships[].balanceintegerRequireditems[].memberships[].reservedintegerRequired- Held by active checkout reservations.
items[].memberships[].availableintegerRequired- balance less reserved, never below zero.
items[].memberships[].lifetimeEarnedintegerRequireditems[].memberships[].joinedAtdate-timeRequireditems[].memberships[].joinedBranchIdstring or nullRequireditems[].memberships[].lastActivityAtdate-timeRequireditems[].memberships[].marketingOptInbooleanRequired- False once the customer, the shop or your service withdrew marketing consent. A membership you create starts false.
items[].memberships[].consentobject or nullRequired- What was recorded when the customer joined; null when none was.
limitintegerRequiredoffsetintegerRequiredhasMorebooleanRequired
curl "https://app.revisit.cards/v1/developer/customers" \
-H "Authorization: Bearer $DEVELOPER_API_KEY"{
"data": {
"hasMore": false,
"items": [
{
"createdAt": "2026-11-03T08:41:12.000Z",
"customerId": "eb83b9c0-5619-432d-b857-cef7fcf84edb",
"externalCustomerId": "user-42",
"locale": "en-BD",
"maskedPhone": "•••• 4821",
"memberCode": "K7M2QX",
"memberships": [
{
"available": 7,
"balance": 7,
"cardType": "stamp",
"consent": {},
"joinedAt": "2026-11-03T08:41:12.000Z",
"joinedBranchId": "1915466d-1961-49fe-b213-b5b7f0692d63",
"lastActivityAt": "2026-11-03T08:41:12.000Z",
"lifetimeEarned": 42,
"marketingOptIn": false,
"membershipId": "b53ca741-1e76-4ddb-b6a5-998b65c4aff3",
"programId": "d7595bbd-4342-41b4-9037-2e61a9f8403f",
"reserved": 0
}
],
"name": "Nusrat Jahan"
}
],
"limit": 50,
"offset": 0
},
"requestId": "ecca2c65-ccbf-47cd-ba79-cdad1e6442c7",
"success": true
}Add or update a customer by your own customer id
/customersScopecustomers:write
Requires customers:write and an Idempotency-Key. Names the customer with your id; every detail is optional. The first call creates the customer, later calls change only the details they carry. Never records or changes marketing consent, never merges customers: a phone that already belongs to another customer, or an id that already points elsewhere, is 409 CUSTOMER_REFERENCE_CONFLICT. The answer holds references only, never what you sent. A key limited to some branches or programs must enroll instead.
Send an Idempotency-Key header. Safe to retry.
Body
externalCustomerIdstringRequired- Your own id for the customer. Unique per business and application, and separate in test and live.
namestring- 1 to 60 characters.
phonestring- A Bangladesh mobile in any form (01XXXXXXXXX or +8801XXXXXXXXX) or another country as +<country><number> (+447700900123). Stored sealed; never returned.
emailemail- Stored sealed; never returned.
localestring- Language tag such as en, bn or en-BD.
Returns 200
createdbooleanRequiredcustomerIdstringRequiredexternalCustomerIdstringRequired
Errors
- 409
CUSTOMER_REFERENCE_CONFLICTthe customer id or the phone points at a different customer than the one this request would create or change. Nothing is merged or moved.
curl -X POST "https://app.revisit.cards/v1/developer/customers" \
-H "Authorization: Bearer $DEVELOPER_API_KEY" \
-H "Idempotency-Key: 5f0c2a8e-7b1d-4c3e-9a6f-0d4b8ed2dddb" \
-H "Content-Type: application/json" \
-d '{
"externalCustomerId": "user-42"
}'{
"data": {
"created": false,
"customerId": "eb83b9c0-5619-432d-b857-cef7fcf84edb",
"externalCustomerId": "user-42"
},
"requestId": "39847d18-6b60-4fb7-bb4f-44b98e9282fb",
"success": true
}Link your customer id to a customer the business already has
/customers/linksScopecustomers:write
Requires customers:write and an Idempotency-Key. Name the existing customer by Revisit's customerId or by the six-character memberCode (case, spaces and hyphens do not matter), or by both when they agree. A phone number or email is never used to find a customer. Nothing about the customer, their cards, balance or consent changes. Afterwards the same id works with enrolling (enrollCustomer finds the existing membership, created: false). An id already linked to a different customer, a customer this application already knows by another id, or two names for different customers is 409 CUSTOMER_REFERENCE_CONFLICT; an unknown, erased or other-business customer is 404. One customer may carry one id per application. The answer holds references only (no contact details, card links or tokens). A key limited to some branches or programs cannot link (403).
Send an Idempotency-Key header. Safe to retry.
Body
externalCustomerIdstringRequired- Your own id for the customer.
customerIduuid- Revisit's id for the customer.
memberCodestring- The member code on the card and at the counter, e.g. K7M4QX or K7M-4QX.
Returns 200
createdbooleanRequired- True when this request made the link (a repeat answers false).
customerIdstringRequiredexternalCustomerIdstringRequiredmembershipsobject[]Required- The customer's memberships in the business, one per program.
memberships[].membershipIdstringRequiredmemberships[].programIdstringRequired
Errors
- 409
CUSTOMER_REFERENCE_CONFLICTthe customer id or the phone points at a different customer than the one this request would create or change. Nothing is merged or moved.
curl -X POST "https://app.revisit.cards/v1/developer/customers/links" \
-H "Authorization: Bearer $DEVELOPER_API_KEY" \
-H "Idempotency-Key: 5f0c2a8e-7b1d-4c3e-9a6f-0d4b8e5c8cb2" \
-H "Content-Type: application/json" \
-d '{
"externalCustomerId": "user-42"
}'{
"data": {
"created": false,
"customerId": "eb83b9c0-5619-432d-b857-cef7fcf84edb",
"externalCustomerId": "user-42",
"memberships": [
{
"membershipId": "b53ca741-1e76-4ddb-b6a5-998b65c4aff3",
"programId": "d7595bbd-4342-41b4-9037-2e61a9f8403f"
}
]
},
"requestId": "43d7ee6a-d433-42f0-9f0c-a0e5cacb67dd",
"success": true
}Count customers
/customers/countScopecustomers:read
Requires customers:read. The customers a list with the same programId and branchId would hold, under the same visibility rules.
Query
programIduuidbranchIduuidsegmentenum- Only the customers one of the app's buckets names, by the rules the dashboard counts with: ready (a reward can be claimed now), one-away (stamp cards: next stamp fills the card), lapsed (joined over 30 days ago, no visit in 30), new (first membership in the last 30 Dhaka days), champions (5 or more visit days in 30). Read card by card over the cards the key may use; a segment that spans more than 24 card and branch reads or 20,000 customers is 422, narrow it with programId or branchId. One of
ready,one-away,lapsed,new,champions.
Returns 200
countintegerRequired
curl "https://app.revisit.cards/v1/developer/customers/count" \
-H "Authorization: Bearer $DEVELOPER_API_KEY"{
"data": {
"count": 12
},
"requestId": "27d25b2c-341a-42e4-92a9-492898fc34c2",
"success": true
}Read a customer's card history
/customers/{id}/historyScopecustomers:read
Requires customers:read. The customer's ledger rows (stamps, points, redemptions, top-ups, spends, bonuses, resets and reversals) on the cards, and for a branch-limited key the branches, the key may use, newest first, in pages of at most 50 (limit, default 25) walked with the opaque next value passed back as cursor. Optional programId and branchId narrow it. Rows with no branch on record are hidden from a branch-limited key. Ledger facts only: delta is the balance change, voided says a later reversal undid the row, and there is no staff name, free text, name or phone. The owner's test rows never appear. Unknown, erased, other-business and out-of-limits customers answer 404; a cursor this API did not return is 422.
Path
iduuidRequired
Query
programIduuidbranchIduuidlimitinteger- 1 to 50. Default
25. cursorstring
Returns 200
nextstring or nullRequired- Pass back as cursor for the next page; null on the last.
itemsobject[]Requireditems[].idstringRequireditems[].membershipIdstringRequireditems[].programIdstringRequireditems[].branchIdstring or nullRequireditems[].typeenumRequired- One of
STAMP,REDEEM,RESET,TOPUP,SPEND,BONUS,VOID. items[].deltaintegerRequired- Change to the balance (negative for a redemption or spend).
items[].amountinteger or nullRequireditems[].rewardNamestring or nullRequireditems[].voidedbooleanRequireditems[].voidsTransactionIdstring or nullRequireditems[].createdAtdate-timeRequired
curl "https://app.revisit.cards/v1/developer/customers/e01b010d-2c65-48e1-83a0-acad79f8d3b4/history" \
-H "Authorization: Bearer $DEVELOPER_API_KEY"{
"data": {
"items": [
{
"amount": 1250,
"branchId": "5eff65d7-d01c-4bd3-b26a-3d06b21fd005",
"createdAt": "2026-11-03T08:41:12.000Z",
"delta": 1,
"id": "d12cc02b-02fe-4a1f-83b0-5414e70fd99c",
"membershipId": "b53ca741-1e76-4ddb-b6a5-998b65c4aff3",
"programId": "d7595bbd-4342-41b4-9037-2e61a9f8403f",
"rewardName": "Free chai",
"type": "STAMP",
"voided": false,
"voidsTransactionId": "a7435d7c-7442-4213-a2d2-a75495daf9bf"
}
],
"next": "eyJvZmZzZXQiOjUwfQ"
},
"requestId": "32972ce8-c708-4fbc-8458-ce030210351f",
"success": true
}Read one customer
/customers/{id}Scopecustomers:read
Requires customers:read. Unknown, erased, other-business and out-of-limits customers all answer 404.
Path
iduuidRequired
Returns 200
customerIdstringRequiredexternalCustomerIdstring or nullRequired- The id this application linked for the customer; null when it has not named them.
memberCodestringRequired- Six characters staff and the customer see, e.g. K7M4QX.
namestringRequiredmaskedPhonestringRequired- Last four digits only, e.g. "•••• 4821"; empty when the customer has no phone.
localestring or nullRequiredcreatedAtdate-timeRequiredmembershipsobject[]Required- Only the memberships in programs this key may use.
memberships[].membershipIdstringRequiredmemberships[].programIdstringRequiredmemberships[].cardTypestringRequiredmemberships[].balanceintegerRequiredmemberships[].reservedintegerRequired- Held by active checkout reservations.
memberships[].availableintegerRequired- balance less reserved, never below zero.
memberships[].lifetimeEarnedintegerRequiredmemberships[].joinedAtdate-timeRequiredmemberships[].joinedBranchIdstring or nullRequiredmemberships[].lastActivityAtdate-timeRequiredmemberships[].marketingOptInbooleanRequired- False once the customer, the shop or your service withdrew marketing consent. A membership you create starts false.
memberships[].consentobject or nullRequired- What was recorded when the customer joined; null when none was.
memberships[].consent.atdate-timeRequiredmemberships[].consent.sourceenumRequired- self - the customer; merchant - the shop's attestation. One of
self,merchant. memberships[].consent.versionstring or nullRequired
curl "https://app.revisit.cards/v1/developer/customers/e01b010d-2c65-48e1-83a0-acad79f8d3b4" \
-H "Authorization: Bearer $DEVELOPER_API_KEY"{
"data": {
"createdAt": "2026-11-03T08:41:12.000Z",
"customerId": "eb83b9c0-5619-432d-b857-cef7fcf84edb",
"externalCustomerId": "user-42",
"locale": "en-BD",
"maskedPhone": "•••• 4821",
"memberCode": "K7M2QX",
"memberships": [
{
"available": 7,
"balance": 7,
"cardType": "stamp",
"consent": {
"at": "2026-11-03T08:41:12.000Z",
"source": "self",
"version": "2026-11-02"
},
"joinedAt": "2026-11-03T08:41:12.000Z",
"joinedBranchId": "1915466d-1961-49fe-b213-b5b7f0692d63",
"lastActivityAt": "2026-11-03T08:41:12.000Z",
"lifetimeEarned": 42,
"marketingOptIn": false,
"membershipId": "b53ca741-1e76-4ddb-b6a5-998b65c4aff3",
"programId": "d7595bbd-4342-41b4-9037-2e61a9f8403f",
"reserved": 0
}
],
"name": "Nusrat Jahan"
},
"requestId": "00609c78-cb53-4728-aa53-0d2d0bf14cad",
"success": true
}Change a customer's details
/customers/{id}Scopecustomers:write
Requires customers:write and an Idempotency-Key. Name the customer by Revisit's id. Only the fields sent change; the answer lists which kinds changed (name, phone, email, locale), never the values. A phone that belongs to another customer is 409 CUSTOMER_REFERENCE_CONFLICT (customers are never merged). Unknown fields (free-form profile data, consent) and an empty body are 422. Consent and opt-outs are never touched. A key limited to some branches or programs cannot change customers (403).
Send an Idempotency-Key header. Safe to retry.
Path
iduuidRequired
Body
namestring- 1 to 60 characters.
phonestring- A Bangladesh mobile in any form or another country as +<country><number>. Stored sealed; never returned.
emailemail- Stored sealed; never returned.
localestring- Language tag such as en, bn or en-BD.
Returns 200
customerIdstringRequiredchangedenum[]Required- Which details this request changed; empty when nothing differed.
Errors
- 409
CUSTOMER_REFERENCE_CONFLICTthe customer id or the phone points at a different customer than the one this request would create or change. Nothing is merged or moved.
curl -X PATCH "https://app.revisit.cards/v1/developer/customers/e01b010d-2c65-48e1-83a0-acad79f8d3b4" \
-H "Authorization: Bearer $DEVELOPER_API_KEY" \
-H "Idempotency-Key: 5f0c2a8e-7b1d-4c3e-9a6f-0d4b8ebe819a" \
-H "Content-Type: application/json" \
-d '{
"name": "Nusrat Jahan",
"phone": "01XXXXXXXXX"
}'{
"data": {
"changed": [
"name"
],
"customerId": "eb83b9c0-5619-432d-b857-cef7fcf84edb"
},
"requestId": "26277370-ac46-49d7-b2ba-ad86bca840b8",
"success": true
}Permanently erase a customer
/customers/{id}Scopecustomers:erase
Requires customers:erase and an Idempotency-Key. This cannot be undone. It is the same erasure the shop's own staff run in the app: the customer's name, phone, email, member code, opt-out link, your ids for them (so the same id adds a new customer later), queued notifications, wallet device registrations and holder names on issued passes are removed or cleared, any details still waiting in an unfinished import row are withdrawn (the row fails CUSTOMER_ERASED), and every export file that held them is wiped. What stays is anonymous: the cards' balances and the transaction ledger, issued passes (unlinked), and the audit trail (which records the key, never a detail). Offers are not sent to an erased customer. The scope is never part of the Read-only or Full access presets. A key limited to some branches or programs cannot erase (403), since a customer belongs to the whole business. A repeat with the same key replays; a new key on an already erased customer answers 200 with alreadyErased: true. Another business's or environment's customer, the owner's test pass and an unknown id are 404.
Send an Idempotency-Key header. Safe to retry.
Path
iduuidRequired
Returns 200
customerIdstringRequirederasedtrueRequiredalreadyErasedbooleanRequired- True when this customer was erased before this request.
curl -X DELETE "https://app.revisit.cards/v1/developer/customers/e01b010d-2c65-48e1-83a0-acad79f8d3b4" \
-H "Authorization: Bearer $DEVELOPER_API_KEY" \
-H "Idempotency-Key: 5f0c2a8e-7b1d-4c3e-9a6f-0d4b8e10935a"{
"data": {
"alreadyErased": false,
"customerId": "eb83b9c0-5619-432d-b857-cef7fcf84edb",
"erased": true
},
"requestId": "fb2b8a51-8315-49ca-8d56-35e53f19c789",
"success": true
}Withdraw a customer's marketing consent
/customers/{id}/marketing-consentScopecustomers:write
Requires customers:write and an Idempotency-Key. Opts the customer out of offers on every card they hold at the business. Only { "marketingOptIn": false } is accepted: giving consent is the customer's own act (join page, card page) or the shop's attested tick, so true is 422 and nothing in the API opts a customer back in. A repeat answers memberships: 0. A key limited to some branches or programs cannot (403).
Send an Idempotency-Key header. Safe to retry.
Path
iduuidRequired
Body
marketingOptInfalseRequired
Returns 200
customerIdstringRequiredmarketingOptInfalseRequiredmembershipsintegerRequired- How many memberships this request switched to opted out (0 on a repeat).
curl -X PUT "https://app.revisit.cards/v1/developer/customers/e01b010d-2c65-48e1-83a0-acad79f8d3b4/marketing-consent" \
-H "Authorization: Bearer $DEVELOPER_API_KEY" \
-H "Idempotency-Key: 5f0c2a8e-7b1d-4c3e-9a6f-0d4b8ecd2fa7" \
-H "Content-Type: application/json" \
-d '{
"marketingOptIn": false
}'{
"data": {
"customerId": "eb83b9c0-5619-432d-b857-cef7fcf84edb",
"marketingOptIn": false,
"memberships": 2
},
"requestId": "6feb707c-9d60-4dd0-a65a-8b7695ba4099",
"success": true
}