Skip to content

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

GET/api/v1/customers

List 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
  }
}
GET/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"
  }
}
GET/api/v1/customers/{id}/transactions

List 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
  }
}
GET/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"
}
POST/api/v1/stamp

Record 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
}
POST/api/v1/stamp/redeem

Give 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"
  }
}
POST/api/v1/stamp/redeem-points

Spend 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.