☰ Menu · POS and cash registers
POS and cash registers
Everything a till needs: find the guest, apply the check, redeem cashback, handle refunds, and issue cards that carry the till's own numbers.
The usual flow at the counter: the cashier scans the guest's wallet card (or types the phone), the till shows the balance, the guest decides whether to pay part of the check with cashback, the check closes and the till sends it to us. The guest's card on the phone updates within seconds.
1. Find the guest
GET /v1/members/{member} accepts whatever the till has: the card number, the barcode content, the phone in international format, or our member id. URL-encode a phone (+ becomes %2B).
# by the number on the card (or the barcode the scanner read)
curl https://loyaltyfy.io/api/v1/members/2000111703779 \
-H "Authorization: Bearer sk_test_..."
# by phone, with fresh add-to-wallet links
curl "https://loyaltyfy.io/api/v1/members/%2B37366791102?expand=wallet" \
-H "Authorization: Bearer sk_test_..."Show the cashier what matters for the program, from the member object:
| program.mechanic | Show |
|---|---|
| cashback | cashback.balance and the most that can be redeemed: the smaller of the balance and redeem_max_percent of the check. |
| stamps | stamps.current of stamps.required, and stamps.rewards_available if the guest has a free item waiting. |
| visit_discount, vip | discount.percent and discount.tier: apply this discount to the check yourself. |
2. What the card's barcode contains
The venue chooses the barcode format in Developers → Card barcode, so it matches what the till's scanner reads:
| Format | The barcode contains | Use when |
|---|---|---|
| QR with the card number | card_number, for example 2000111703779 | The scanner reads QR and the till searches by card number. Recommended. |
| Barcode (Code 128) with the card number | The same number as a linear barcode, printed under the bars | Older laser scanners that cannot read QR. |
| QR, Loyaltyfy cashier app | Our internal serial (a UUID) | The default. Only the Loyaltyfy cashier app reads it; a POS can still find the guest by phone. |
Changing the format updates the cards already in guests' wallets; no one needs to reinstall anything. Whatever the format, send the scanned value as is to GET /v1/members/{member} or to POST /v1/scans, which also strips scanner prefixes and line breaks.
3. Issue cards with the till's own numbers
If the till already has a card system, keep its numbers. Enrol the guest with card_number set to the till's number; the wallet card shows that number and its barcode encodes it, so the scanner finds the guest in the till's own database too. Numbers are unique per venue: a duplicate gets 409 card_number_taken. To give an existing member a till number later, use PATCH /v1/members/{member} with card_number.
curl https://loyaltyfy.io/api/v1/members \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7d1b2c4e-enrol-000123" \
-d '{
"phone": "+37366791102",
"first_name": "Ion",
"last_name": "Popescu",
"card_number": "2000111703779",
"language": "ro",
"consent": true
}'The response has wallet.apple_url and wallet.google_url. Print them as a QR on the receipt, send them by SMS, or show them on the customer display: the guest opens the link and the card is added to the phone.
4. Apply the check
Send each closed check once, after payment. amount is the full check before any cashback; redeem_amount is how much of it the guest paid with cashback. We apply the program's rules (minimum check, redeem cap, tiers) the same way the venue's cashier screen does.
curl https://loyaltyfy.io/api/v1/transactions \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{
"external_id": "CHK-2026-000123",
"member": "2000111703779",
"amount": 25000,
"currency": "MDL",
"register_id": "POS-1",
"closed_at": "2026-10-06T18:17:00+03:00",
"line_items": [
{ "sku": "1001", "name": "Espresso", "quantity": 2, "unit_price": 4000, "category": "Coffee" },
{ "sku": "2005", "name": "Cheesecake", "quantity": 1, "unit_price": 17000, "category": "Desserts" }
]
}'{
"object": "transaction",
"id": "f8acbb16-0b3b-4afc-acbb-e5eabd4dd072",
"external_id": "CHK-2026-000123",
"status": "completed",
"amount": 25000,
"redeem_amount": 0,
"cashback_earned": 1250,
"stamps_earned": 0,
"reward_unlocked": false,
"net_amount": 25000,
"currency": "MDL",
"register_id": "POS-1",
"closed_at": "2026-10-06T15:17:00+00:00",
"livemode": false,
"created_at": "2026-10-06T18:17:17.921+00:00",
"voided_at": null,
"member": {
"object": "member",
"id": "6dacfe0a-2500-44c6-a37c-05c660b093b4",
"card_number": "2000111703779",
"cashback": { "balance": 1250, "currency": "MDL", "earn_rate": 5, ... },
...
}
}external_id must be unique across all tills of the venue. If check numbers restart on each till or each shift, combine them, for example POS-3-000123 (till 3, check 123).Cashback is earned on what the guest actually paid: amount − redeem_amount. For stamp and visit programs the check counts as a visit; stamps_earned and reward_unlocked tell you what happened, so the till can say "one more coffee and the next is free".
5. Pay with cashback
Ask for the balance first, let the guest choose, then send the check with redeem_amount. If the balance changed in between (the guest paid somewhere else a second ago), you get 402 insufficient_balance with the current available amount; show it and let the cashier retry.
curl https://loyaltyfy.io/api/v1/transactions \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{
"external_id": "CHK-2026-000124",
"member": "2000111703779",
"amount": 12000,
"redeem_amount": 1250
}'{
"error": {
"type": "invalid_request_error",
"code": "insufficient_balance",
"message": "redeem_amount is more than the available balance.",
"param": "redeem_amount",
"available": 1250
},
"request_id": "req_6f4c30b8ca7d4423a9d4"
}201 or 200. Send the check when its total is final but before taking payment. If there is no answer, retry with the same external_id, or close the check without cashback. Otherwise the guest can get the discount and keep the balance.6. Give a stamp reward
When stamps.rewards_available is above zero the guest has a free item. Give it on the till and record it:
curl -X POST https://loyaltyfy.io/api/v1/members/4285324286429/rewards/redeem \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: reward-4285324286429-0001"7. Refunds and mistakes
Void the whole check by its external_id. Earned cashback or stamps are taken back, redeemed cashback is returned to the guest. Partial refunds: void the check and send a new one with the corrected amount and a new external_id.
curl -X POST https://loyaltyfy.io/api/v1/transactions/CHK-2026-000123/void \
-H "Authorization: Bearer sk_test_..."8. When the till is offline
Queue checks locally with their external_id and closed_at and send them when the connection is back. Duplicates are ignored, so it is safe to resend the whole queue. Lookups need the network; if it is down, let the cashier skip loyalty for that check rather than block the sale.
Checks pushed from the till's server
Some POS systems cannot call an API at the counter but can send closed checks from their server. That works too: send the same POST /v1/transactions from the server, with the card number the cashier scanned, as soon as the check is closed. Subscribe to transaction.completed if the till wants to show the result later.