☰ Menu · Kiosks and scanners
Guides

Kiosks and scanners

Devices without a till: a scanner at the entrance, a tablet where guests sign up, a counter scanner that gives out rewards.

Give each device its own key with only the devices:write scope. It can scan cards, record visits, sign guests up and give out rewards, but it cannot list members or send checks. If the tablet is stolen, revoke that one key.

Scan anything

Send exactly what the scanner produced to POST /v1/scans. We handle the different shapes for you: a card number, a card QR, a Code 128 with the ]C1 symbology prefix, a trailing Enter or Tab from a keyboard-wedge scanner, a link to the card page, or a phone number typed on a kiosk.

curl
curl https://loyaltyfy.io/api/v1/scans \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{ "code": "]C14285324286429\r", "device_id": "door-1" }'
Response · 200
{
  "object": "scan",
  "matched": true,
  "matched_by": "4285324286429",
  "device_id": "door-1",
  "mechanic": "stamps",
  "member": {
    "object": "member",
    "card_number": "4285324286429",
    "first_name": "Ana",
    "stamps": { "current": 1, "required": 5, "rewards_available": 0 },
    ...
  },
  "actions": [
    { "type": "visit", "method": "POST", "path": "/v1/visits" },
    { "type": "transaction", "method": "POST", "path": "/v1/transactions" }
  ]
}

actions lists what this device can do next for this guest, so the screen can show the right buttons without knowing the program's rules. When the guest has a reward waiting, a redeem_reward action appears with the path to call.

JSON
"actions": [
  { "type": "visit", "method": "POST", "path": "/v1/visits" },
  { "type": "transaction", "method": "POST", "path": "/v1/transactions" },
  {
    "type": "redeem_reward",
    "method": "POST",
    "path": "/v1/members/4285324286429/rewards/redeem",
    "available": 1
  }
]

Stamp a visit without a check

For stamp and visit programs, a check-in is enough: a gym door, a car wash bay, a coffee counter that does not run a till. POST /v1/visits gives the per-visit reward of the program. Send a unique external_id for each check-in (for example the device name plus a counter); a repeat is ignored.

curl
curl https://loyaltyfy.io/api/v1/visits \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "door-1-2026-10-06-0042",
    "member": "4285324286429",
    "device_id": "door-1"
  }'
Response · 201
{
  "object": "visit",
  "id": "feb2ad75-ac7b-4bb6-8b64-192323b7f6ea",
  "external_id": "door-1-2026-10-06-0042",
  "status": "completed",
  "amount": 0,
  "stamps_earned": 1,
  "reward_unlocked": false,
  "register_id": "door-1",
  "livemode": false,
  "created_at": "2026-10-06T18:17:28.209+00:00",
  "member": {
    "object": "member",
    "card_number": "4285324286429",
    "stamps": { "current": 2, "required": 5, "rewards_available": 0 },
    ...
  },
  ...
}
Cashback programs earn on money, not on visits, so a visit for a cashback member is refused with 400 visit_not_supported. The actions of a scan never offer visit for such members.

To avoid stamping twice when a guest scans twice in a row, build the external_id from the member and the time window, for example door-1-4285324286429-2026-10-06T18: one stamp per hour at most.

Sign-up kiosk

A tablet at the entrance asks for a phone and a name, shows the privacy terms, and calls POST /v1/members with consent: true only after the guest agrees. Then show wallet.apple_url or wallet.google_url as a QR: the guest scans it and the card goes straight into their wallet.

curl
curl https://loyaltyfy.io/api/v1/members \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: kiosk-3-0193" \
  -d '{ "phone": "+306912345678", "first_name": "Eleni", "language": "el", "consent": true }'

# show response.wallet.apple_url or google_url as a QR on the kiosk screen

If the phone is already enrolled, the call returns the existing member with 200, so a guest who lost the card simply gets it again.

Questions about an integration: api@loyaltyfy.io. We answer within one business day.