POS integration.
Build a point-of-sale integration for member lookup, balance checks, purchases, rewards, stamps, and vouchers.
This guide shows the typical cashier workflow: identify the member, inspect their current loyalty state, record the sale, then redeem a reward, stamp card, or voucher when the member asks.
Use this guide for:
- in-store POS systems
- kiosk terminals
- cashier tablets
- back-office terminals that behave like a POS
Recommended scopes
The Partner key creation form selects View only, which cannot write purchases or redeem rewards. Select Point of Sale explicitly to grant the following scopes.
Start with:
readwrite:transactionswrite:rewards
Add these only if you need them:
write:stampswrite:vouchers
Core flow
- Find or search the member.
- Check the member's balance on the active card.
- Record the purchase.
- If the member asks, redeem a reward, add stamps, or process a voucher.
- Persist the external order or ticket reference on your side for reconciliation.
For a brand-new member, step 1 answers not-found by design. See First-time customers.
Member lookup
The partner API supports multiple member identifier formats. This is useful because different POS systems know different identifiers at checkout time.
Accepted forms:
- member UUID
- member number
- unique identifier
Examples:
{ "member_identifier": "[email protected]" }
{ "member_identifier": "MEM-2024-00001" }
{ "member_identifier": "550e8400-e29b-41d4-a716-446655440000" }
{ "member_identifier": "344-319-665-971" }
Exact lookup:
GET /api/agent/v1/partner/members/[email protected] HTTP/1.1
Host: your-domain.com
Accept: application/json
X-Agent-Key: rl_agent_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0
Search fallback:
GET /api/agent/v1/partner/members?search=jane&per_page=25 HTTP/1.1
Host: your-domain.com
Accept: application/json
X-Agent-Key: rl_agent_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0
First-time customers
Lookup and search return only members who have already interacted with your business: earned points, collected a stamp, redeemed a voucher, or used a pass. A member who joined a card seconds ago, for example by scanning its QR code, has no interaction yet, so lookup answers not-found even though the member exists.
The transaction endpoints resolve member_identifier without the interaction requirement, so skip the lookup and record the purchase with the identifier the customer presents: their email, their member number, or the 12-digit identifier shown with the QR code in the member app. The QR carries a persistent member-card code for the built-in staff scanner. The Agent purchase endpoint does not accept that QR value. Ask for an email, member number, or unique identifier. To find Your identifier, the member taps the QR to open its larger view.
That first purchase completes everything on its own. It enrolls the member on the card if they had not joined it yet and credits any initial bonus points the card offers on top of the points for the sale. From then on, lookup and search find the member.
This gives you the fastest flow at a busy counter: the customer joins by scanning the card's QR code on their own phone, with no POS work, and their first order activates the membership. To mirror new members into your POS customer records as they appear, subscribe to webhooks and upsert from whichever event arrives first. See member synchronization.
Balance check
Once you know the member and the active card, fetch the live balance:
GET /api/agent/v1/partner/members/550e8400-e29b-41d4-a716-446655440000/balance/3598a5db-f008-477c-ac4d-5365c662ad62 HTTP/1.1
Host: your-domain.com
Accept: application/json
X-Agent-Key: rl_agent_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0
{
"error": false,
"data": {
"member_id": "550e8400-e29b-41d4-a716-446655440000",
"card_id": "3598a5db-f008-477c-ac4d-5365c662ad62",
"balance": 420,
"currency": "USD"
}
}
Record the purchase
Use POST /partner/transactions/purchase to award points for the sale.
POST /api/agent/v1/partner/transactions/purchase HTTP/1.1
Host: your-domain.com
Accept: application/json
Content-Type: application/json
X-Agent-Key: rl_agent_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0
{
"card_id": "3598a5db-f008-477c-ac4d-5365c662ad62",
"member_identifier": "[email protected]",
"purchase_amount": 24.50,
"staff_id": "9a126c5f-8737-4f0a-83b2-11a6b7e8f901",
"note": "Order #12345"
}
{
"error": false,
"data": {
"transaction_id": "019cce5d-c950-71d7-8f6f-731f06c3cf56",
"points_awarded": 2450,
"member_balance": 2870,
"purchase_amount": 24.5,
"card_id": "3598a5db-f008-477c-ac4d-5365c662ad62",
"member_id": "550e8400-e29b-41d4-a716-446655440000"
}
}
Make the sale retry-safe
Add source_reference, currency, and source_completed_at and the same sale can be sent as often as the till needs to, awarding points exactly once.
POST /api/agent/v1/partner/transactions/purchase HTTP/1.1
Host: your-domain.com
Accept: application/json
Content-Type: application/json
X-Agent-Key: rl_agent_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0
{
"card_id": "3598a5db-f008-477c-ac4d-5365c662ad62",
"member_identifier": "[email protected]",
"purchase_amount": "24.50",
"currency": "EUR",
"source_completed_at": "2026-08-26T09:41:07+02:00",
"source_reference": "pos:store-1:till-3:98441",
"staff_id": "9a126c5f-8737-4f0a-83b2-11a6b7e8f901",
"note": "Order #12345"
}
The first call returns 201 with replayed: false; an identical retry returns 200 with replayed: true and the original facts. Build the reference from what the source system already owns (platform, store, till, sale id), keep it out of customer data, and reuse it for retries of that one sale only. The full field rules and every response code are in Record a purchase.
staff_id delegation
- Without
staff_id, Reward Loyalty records the operation as an agent or system transaction. - With
staff_id, it attributes the operation to that staff member. - The
staff_idmust belong to the same partner, and that staff member must have access to the card's club.
Use staff_id when the terminal knows which cashier is signed in.
Points-only adjustments
If your POS needs to grant a manual point amount instead of calculating from a purchase amount, send points.
POST /api/agent/v1/partner/transactions/purchase HTTP/1.1
Host: your-domain.com
Accept: application/json
Content-Type: application/json
X-Agent-Key: rl_agent_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0
{
"card_id": "3598a5db-f008-477c-ac4d-5365c662ad62",
"member_identifier": "MEM-2024-00001",
"points": 500,
"note": "Manual goodwill adjustment"
}
Redeem a reward
Use POST /partner/transactions/redeem when the member spends points at checkout.
POST /api/agent/v1/partner/transactions/redeem HTTP/1.1
Host: your-domain.com
Accept: application/json
Content-Type: application/json
X-Agent-Key: rl_agent_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0
{
"card_id": "3598a5db-f008-477c-ac4d-5365c662ad62",
"reward_id": "7f1335c0-b6f1-4c82-8655-c5148d3fd3be",
"member_identifier": "[email protected]",
"staff_id": "9a126c5f-8737-4f0a-83b2-11a6b7e8f901",
"note": "Redeemed on POS ticket #12345"
}
{
"error": false,
"data": {
"transaction_id": "019cce5d-cae0-70b8-9af1-7158fca5bcdb",
"points_deducted": 100,
"member_balance": 2770,
"new_balance": 2770,
"reward_id": "7f1335c0-b6f1-4c82-8655-c5148d3fd3be",
"reward": {
"id": "7f1335c0-b6f1-4c82-8655-c5148d3fd3be",
"title": "Free Coffee"
},
"card_id": "3598a5db-f008-477c-ac4d-5365c662ad62",
"member_id": "550e8400-e29b-41d4-a716-446655440000"
}
}
Typical failure branch:
{
"error": true,
"code": "INSUFFICIENT_POINTS",
"message": "Member does not have enough points for this reward.",
"retry_strategy": "no_retry",
"details": {
"required": 100,
"available": 20
}
}
Stamp cards
Add stamps:
POST /api/agent/v1/partner/stamp-cards/{stampCardId}/stamps HTTP/1.1
Host: your-domain.com
Accept: application/json
Content-Type: application/json
X-Agent-Key: rl_agent_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0
{
"member_identifier": "[email protected]",
"stamps": 1,
"purchase_amount": 12.00,
"note": "POS ticket #12345"
}
Redeem a completed stamp reward:
POST /api/agent/v1/partner/stamp-cards/{stampCardId}/redeem HTTP/1.1
Host: your-domain.com
Accept: application/json
Content-Type: application/json
X-Agent-Key: rl_agent_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0
{
"member_identifier": "[email protected]",
"note": "POS redemption"
}
Vouchers
Validate the code before you finalize the order:
POST /api/agent/v1/partner/vouchers/validate HTTP/1.1
Host: your-domain.com
Accept: application/json
Content-Type: application/json
X-Agent-Key: rl_agent_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0
{
"code": "WELCOME10",
"member_identifier": "[email protected]",
"club_id": "019cce5d-bd81-7096-9118-9cb360ab9008",
"order_amount": 2450
}
Then redeem the voucher:
POST /api/agent/v1/partner/vouchers/{voucherId}/redeem HTTP/1.1
Host: your-domain.com
Accept: application/json
Content-Type: application/json
X-Agent-Key: rl_agent_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0
{
"member_identifier": "[email protected]",
"order_amount": 2450,
"order_reference": "12345"
}
Safe retry behavior
A purchase sent with source_reference is safe to retry: the same reference awards points once per business, and a later identical send returns the original result with replayed: true instead of awarding again. Send it on every sale a till or automation posts unattended. A retry can still meet a transient state, such as the sale being processed or the app being briefly busy, so branch on the code you get back, using the response table.
Without that reference, a purchase is not safe to retry, and redemptions and deductions never are.
- Retry an identical request on
503withretry_strategy: backoff, and back off between attempts. - Stop on
409 PURCHASE_REFERENCE_CONFLICT: that reference already records a different sale. - Use your own external order reference tracking for redemptions and deductions.
- Pass the order or ticket ID in
notewhere available. - Reconcile ambiguous failures before replaying anything without a reference.
If you hit a rate limit:
{
"error": true,
"code": "RATE_LIMITED",
"message": "Too many requests. Please slow down.",
"retry_strategy": "backoff"
}
Honor Retry-After.