Managed plan
Stamp a card from the system you already run.
Your till, your booking software or your back office can read the balances on your loyalty cards and move them. The wallet pass on your customer’s phone updates afterwards on its own. There is nothing for them to open and nothing for you to trigger.
- Base URL
- https://api.loyalisto.com
- Machine-readable
- /api/openapi.jsonOpenAPI 3.1
Authentication
Mint a key in Settings → API. It begins with loy_ and is scoped to your business alone. Present it either way.
A key opens the endpoints on this page and nothing else: not billing, not staff, not your branding, not an export of your customer list. It is a long-lived credential that lives inside somebody else’s software, so it is scoped for that.
curl https://api.loyalisto.com/api/v1/customers \
-H "x-api-key: loy_..."curl https://api.loyalisto.com/api/v1/stamp \
-H "Authorization: Bearer loy_..." \
-H "Content-Type: application/json" \
-d '{"qrCode":"abc123xyz","quantity":1}'When it goes wrong
- 401
- The key is missing, revoked or expired.
- 403
- This key may not reach this route, or the card belongs to another business.
- 404
- No such card.
- 409
- The card changed while you were writing to it. Read it again and retry.
- 429
- Rate limited.
Endpoints
/api/v1/customersList the customers holding a card
Every customer enrolled in one of your programmes, with their current balance. Paginated, newest activity first.
Parameters
pagequery- Page number. Defaults to 1.
limitquery- Rows per page, up to 100. Defaults to 20.
searchquery- Match on name, email or phone.
Response
{
"data": [
{
"id": "card_8sk2md",
"customerId": "cus_2ja91z",
"name": "Amina Ben Salah",
"email": "amina@example.com",
"phone": "+21620123456",
"stampCount": 7,
"pointBalance": 0,
"visitCount": 7,
"tier": null,
"lastActivityAt": "2026-09-12T09:31:04.000Z",
"enrolledAt": "2026-06-02T14:05:00.000Z",
"program": {
"name": "Coffee card",
"type": "STAMP_CARD",
"config": {
"stampsRequired": 10
}
},
"customFields": null
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 1,
"totalPages": 1
}
}/api/v1/customers/{id}Read one customer card
The card, its holder and its history. The id is the card id returned by the list.
Parameters
idpath · required- Card id.
Response
{
"id": "card_8sk2md",
"name": "Amina Ben Salah",
"stampCount": 7,
"pointBalance": 0,
"tier": null,
"program": {
"name": "Coffee card",
"type": "STAMP_CARD"
}
}/api/v1/customers/{id}/transactionsList one card’s transactions
Everything that has moved this card’s balance, most recent first.
Parameters
idpath · required- Card id.
pagequery- Page number. Defaults to 1.
limitquery- Rows per page, up to 100. Defaults to 50.
typequery- Filter by transaction type.
Response
{
"data": [
{
"id": "txn_44kd0a",
"type": "STAMP",
"value": 1,
"createdAt": "2026-09-12T09:31:04.000Z"
}
],
"pagination": {
"page": 1,
"limit": 50,
"total": 1,
"totalPages": 1
}
}/api/v1/scan/{qrCode}Look up a card by the code on it
Resolves the code a till or scanner just read into the card it belongs to, with the balance and what the next reward needs. A card belonging to another business answers 403, never a balance.
Parameters
qrCodepath · required- The code carried by the customer’s card.
Response
{
"card": {
"id": "card_8sk2md",
"stampCount": 7,
"pointBalance": 0,
"tier": null
},
"customer": {
"name": "Amina Ben Salah"
},
"program": {
"name": "Coffee card",
"type": "STAMP_CARD"
},
"currency": "USD"
}/api/v1/stampRecord a visit
Adds stamps or points to the card behind a code. The wallet pass updates on the customer’s phone shortly afterwards; the call does not wait for it. Send spendAmount for a programme that earns per unit spent.
Body
Required: qrCode
{
"qrCode": {
"type": "string",
"description": "The code carried by the customer’s card."
},
"quantity": {
"type": "integer",
"minimum": 1,
"default": 1,
"description": "How many stamps, or how many times to apply the earn rule."
},
"spendAmount": {
"type": "number",
"exclusiveMinimum": 0,
"description": "What they spent, in the shop’s own currency. Required by programmes that earn per unit spent."
},
"category": {
"type": "string",
"description": "Category name, for a programme with bonus categories."
},
"note": {
"type": "string",
"description": "Free text kept on the transaction."
}
}Response
{
"status": "success",
"transaction": {
"id": "txn_44kd0a",
"type": "STAMP",
"value": 1
},
"card": {
"stampCount": 8,
"pointBalance": 0,
"visitCount": 8,
"tier": null
},
"rewardTriggered": false,
"rewardName": null,
"redemption": null
}/api/v1/stamp/redeemGive a reward
Marks a reward as handed over and resets whatever the programme resets.
Body
Required: cardIdrewardId
{
"cardId": {
"type": "string",
"description": "Card id."
},
"rewardId": {
"type": "string",
"description": "The reward being given."
}
}Response
{
"status": "success",
"redemption": {
"id": "red_91kd2x",
"code": "A4F2QK"
}
}/api/v1/stamp/redeem-pointsSpend points
Takes points off a card, for a programme where points buy something priced in points.
Body
Required: cardIdpoints
{
"cardId": {
"type": "string",
"description": "Card id."
},
"points": {
"type": "integer",
"minimum": 1,
"description": "How many points to take off."
}
}Response
{
"status": "success",
"card": {
"pointBalance": 120
}
}Need the API?
It arrives on Managed. You can see your own card and set your programme up before you pay for it.