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.
Identifiers
Three id spaces, only one of which is yours:company is derived from your token and must never be sent — it returns 400.
Errors
Branch on
error.code, never on the message text.
409 on enroll is the happy path for a retry. It means the membership already exists, which is exactly what you wanted. Catch it, then GET /v1/loyalty/memberships?customer_id=<yours> to fetch the one that is already there rather than treating it as a failure.Rate limits
Loyalty shares the per-project quota: 1,000 requests a day across the whole public API. A POS that accrues on every sale is the workload to size for. One sale is onePOST .../ledger — a store doing 300 transactions a day spends a third of the quota on loyalty alone, before anything else your integration does.
Accrue once per transaction
Not once per line item. It is also the only way to stay safe against the concurrency caveat above.
Cache programs and templates
They are authored in the portal and change rarely. Fetch at boot, not per sale.
Skip the balance read
The response to a ledger write already carries
balance_after. A follow-up GET on the membership is a wasted request.Own project for the POS
The quota is per project, so a busy till cannot starve your transactional path if it lives in its own.
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.