The data model
Program
The rules. One of three kinds —
points, stamps, or tiers — and only the matching rule set is populated.Membership
One customer inside one program. Carries the balance, the stamp count, the tier, and the serial number the wallet card is keyed by.
Ledger entry
One movement: an accrual, a redemption, or a correction. Records the balance it resulted in.
Wallet template
The card design for a program — colours, fields, barcode. Versioned, and one version is published.
The three program types
The program decides which metric a movement touches. You never pass a metric —
POST .../ledger on a stamps program moves stamps, on a points program moves points.
What the API does and does not do
Writes are deliberately narrow. Everything the API can write is something a system does many times a day; everything it cannot is something a person configures once.
Endpoints
Programs and templates
Memberships and ledger
Wallet passes
A full round trip
Enroll a customer, give them the card, and start accruing.The customer identifier is yours
customer.id is a free-text string that you choose. It is not a Keebai contact id, not a CRM customer id, and nothing validates that it points at anything.
Whatever you send at enrollment is copied into the membership and never resolved again. Rename the customer in your CRM and the membership keeps the old name until you enroll them somewhere new.
Pick something stable and reproducible — your POS customer id, your CRM primary key. It is the value you will pass to
customer_id to find the membership again, and it is what makes enrollment idempotent: a second enrollment with the same customer.id in the same program returns 409 instead of creating a duplicate.GET /v1/loyalty/memberships?phone=912345678 matches on a digit subsequence, so a stored +56 9 1234 5678 is found without you normalising anything.
Balances are a projection
The stored balance on a membership is maintained as movements happen; it is not summed from the ledger on read. Two consequences matter: Point expiry is a read-time view, not a movement. When a program setsrules.points.expiration_days and the window since enrolled_at has passed, GET on the membership reports balance_points: 0 — but no ledger entry is written and the stored balance is untouched. If that customer then earns points, the new balance continues from the old stored value, not from zero. The ledger’s balance_after is the honest number; the membership’s balance_points is the displayed one.
Scoping
Loyalty is company-wide. No loyalty record carries a project, so every read and write covers your whole company regardless of which project the token was minted for. Same behaviour as CRM reads, and unlike messaging, where writes stay inside the token’s project. Theloyalty feature flag gates the portal, not the API. A company without loyalty enabled that calls with a scoped token gets empty lists, not a 403.
Next steps
Enroll a customer
The first write you will make.
Record a movement
Accrue, redeem, and correct.
Issue a wallet pass
Put the card on the customer’s phone.
Scopes
loyalty:read and loyalty:write.