Loyalty
SmileRewards owns the loyalty points ledger and all money math. Customer 360 owns the identity: which unified customer a SmileRewards memberId belongs to. This makes Customer 360 the single dedup authority, so a member is never double-created. These endpoints take the loyalty scope.
What Customer 360 owns
A customer's SmileRewards memberId is stored as an identity row on the customer. A uniqueness constraint guarantees one customer per member id, and the service enforces one member per customer, merge-safe: a late merge repoints the member onto the survivor.
The bridge flow is: before creating a member on SmileRewards, ask Customer 360 whether the merge-resolved customer already has one. If not, create it on SmileRewards, then record the id back here. Because the record call is idempotent and the dedup authority, a member is never double-created even though SmileRewards does not enforce uniqueness itself.
Customer 360 computes no loyalty points. It resolves and records the member id only. The points ledger, earn rules, and money all live in SmileRewards.
Resolve or record a member
POST /v1/loyalty/members/resolve resolves a customer's member id, and records one if memberId is supplied. It is idempotent and merge-safe.
Identify the customer by customerId (a customer public id from the feed) or zbIdSubject; at least one is required, else invalid_selector (422).
- Resolve only (omit
memberId): the response says whether a member must be created vianeedsMember. - Record (include
memberId): attach the id the bridge just created on SmileRewards.
On a record call, the response tells you which happened:
- Name
created: true- Description
The id was newly attached to this customer.
- Name
created: false, alreadyMember: true- Description
The same id was already attached.
- Name
created: false, conflict: customer_has_other- Description
The customer already had a DIFFERENT member id (returned in
memberId, withattemptedMemberId). Orphan the fresh SmileRewards member.
A memberId already mapped to a different customer returns 409 member_taken with detail.memberId and detail.ownerCustomerPublicId.
Resolve only
curl -X POST https://c360-api.lioncapventures.com/v1/loyalty/members/resolve \
-H "X-API-Key: $C360_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "customerId": "cus_a1b2c3" }'
Resolve only (needs a member)
{
"success": true,
"data": {
"customerPublicId": "cus_a1b2c3",
"zbIdSubject": "b1e5c8a0-...",
"platform": "smilerewards",
"memberId": null,
"hasMember": false,
"created": false,
"linkedAt": null,
"needsMember": true
}
}
Record a member
curl -X POST https://c360-api.lioncapventures.com/v1/loyalty/members/resolve \
-H "X-API-Key: $C360_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "customerId": "cus_a1b2c3", "memberId": "SR-90210" }'
Record (created)
{
"success": true,
"data": {
"customerPublicId": "cus_a1b2c3",
"zbIdSubject": "b1e5c8a0-...",
"platform": "smilerewards",
"memberId": "SR-90210",
"hasMember": true,
"created": true,
"linkedAt": "2026-08-10T09:20:00.000Z",
"needsMember": false
}
}
member_taken (409)
{
"success": false,
"error": {
"code": "member_taken",
"message": "That memberId is already mapped to a different customer.",
"detail": {
"memberId": "SR-90210",
"ownerCustomerPublicId": "cus_zzz999"
}
}
}
Read the current mapping
GET /v1/loyalty/members returns the current SmileRewards member mapping for a customer, read-only. Query by customerId or zbIdSubject. It returns hasMember: false when the customer is unenrolled, and duplicateMemberIds (a reconciliation flag) if a merge ever left more than one member on the customer.
Request
curl "https://c360-api.lioncapventures.com/v1/loyalty/members?customerId=cus_a1b2c3" \
-H "X-API-Key: $C360_API_KEY"
Response (200)
{
"success": true,
"data": {
"customerPublicId": "cus_a1b2c3",
"zbIdSubject": "b1e5c8a0-...",
"platform": "smilerewards",
"memberId": "SR-90210",
"hasMember": true,
"created": false,
"linkedAt": "2026-08-10T09:20:00.000Z",
"needsMember": false
}
}