Loyalty integration
The loyalty integration lets an external loyalty provider keep its members’ cards in KatalystOS, so the restaurant’s staff and receipts see the same card. Your system owns the card number; KatalystOS stores the card under the restaurant’s loyalty program and keeps punches, rewards and a units ledger on it.
All endpoints are under /v1/integration/loyalty/ and need a token with the
integration.loyalty permission. Examples use the sandbox and assume $T holds your token.
1. Create a card
Section titled “1. Create a card”Send your card number and whatever cardholder details you have. The number is the card’s identity within the program.
curl -X POST https://dev-api.katalystos.com/v1/integration/loyalty/card/ \ -H "X-KATALYST-AUTH-TOKEN: $T" -H "Content-Type: application/json" \ -d '{ "number": "WD-100234", "cardholder_email": "ana@example.com", "cardholder_phone": "(555) 123-4567", "cardholder_first_name": "Ana", "cardholder_last_name": "Diaz" }'{ "success": true, "item": { "_id": "6650f0c2a1b2c3d4e5f60718", "number": "WD-100234", "physical_card_number": null, "provider": "wedu", "is_active": true, "balance": 0, "points": 0, "points_lifetime": 0, "units": null, "punches": null, "cardholder_email": "ana@example.com", "cardholder_phone": "5551234567", "cardholder_first_name": "Ana", "cardholder_last_name": "Diaz" }}(Envelope fields are left out of the examples on this page.)
- Idempotent on
number. If a card with that number already exists in the program you get it back unchanged; the cardholder fields you sent are not applied to it. Retrying a timed-out create is safe. - Keep the
_id. The card endpoints below address the card by_id. - Phone numbers are stored as digits only; emails are stored trimmed and in lower case.
providerdefaults towedu, the only external provider today.
2. Find a card
Section titled “2. Find a card”Look a card up by exactly one of number, cardholder_phone or cardholder_email:
curl "https://dev-api.katalystos.com/v1/integration/loyalty/card/?cardholder_phone=555-123-4567" \ -H "X-KATALYST-AUTH-TOKEN: $T"If you send more than one, number wins, then phone, then email. A phone or email that matches
more than one card fails with Multiple cards match; look the card up by number.; use the number.
3. Correct a card number
Section titled “3. Correct a card number”The number is meant to be stable. If one was saved with a typo, replace it:
-
With the card id:
PUT /v1/integration/loyalty/card/{card_id}/with{"number": "WD-100243"}. -
Without it, by the cardholder’s email:
PUT /v1/integration/loyalty/card/with{"cardholder_email": "ana@example.com", "number": "WD-100243"}. This fails if the email matches more than one card.
Both fail with This card number is already in use. if another card in the program has the new
number. Past operations keep the number they were recorded with.
4. Punches and rewards
Section titled “4. Punches and rewards”A restaurant can run punch-card programs (“buy 9 coffees, the 10th is free”). KatalystOS gives you the
punch_card_program_id when one is set up for your integration. Add punches for purchases made in
your system:
curl -X POST https://dev-api.katalystos.com/v1/integration/loyalty/card/6650f0c2a1b2c3d4e5f60718/punch/ \ -H "X-KATALYST-AUTH-TOKEN: $T" -H "Content-Type: application/json" \ -d '{"punch_card_program_id": "6650f1d9a1b2c3d4e5f60720", "count": 1}'{ "success": true, "punches": 4, "target_count": 10, "item": { "_id": "6650f0c2a1b2c3d4e5f60718", "punches": { "6650f1d9a1b2c3d4e5f60720": 4 } }}count is 1 to 100 (default 1). When the card reaches target_count the program’s reward is issued
automatically. List the card’s rewards, newest first:
curl "https://dev-api.katalystos.com/v1/integration/loyalty/card/6650f0c2a1b2c3d4e5f60718/rewards/?status=issued" \ -H "X-KATALYST-AUTH-TOKEN: $T"{ "success": true, "items": [ { "_id": "6651a0b3a1b2c3d4e5f60801", "code": "K7QH2M", "name": "Free coffee", "type": "free_item", "value": null, "source": "punch_card", "status": "issued", "expires_at": "2026-12-31 23:59:59", "redeemed_at": null, "created_at": "2026-11-06 09:12:44" } ]}status is one of issued, redeemed, expired, revoked; leave it out to get all of them. The
list returns at most 200 rewards.
5. Units
Section titled “5. Units”Units are a ledger of free-form quantities per card: gallons of fuel, kWh, miles, anything your program counts. They are stored only; they do not change points or balance.
Record units. Values are deltas, keyed by a unit code you choose:
curl -X POST https://dev-api.katalystos.com/v1/integration/loyalty/card/6650f0c2a1b2c3d4e5f60718/units/ \ -H "X-KATALYST-AUTH-TOKEN: $T" -H "Content-Type: application/json" \ -d '{"units": {"gallons_diesel": 12.4}}'The response is the card, with units holding the running total per code. One call is one ledger
entry, even when it carries several codes.
- A code cannot be empty, contain
., or start with$. - A negative value corrects an earlier entry (send
-9to undo a10that should have been1), but no total may go below zero:Correction would take 'gallons_diesel' below zero.
Read the history, newest first, with limit (1 to 200, default 50) and offset:
curl "https://dev-api.katalystos.com/v1/integration/loyalty/card/6650f0c2a1b2c3d4e5f60718/units/?limit=20" \ -H "X-KATALYST-AUTH-TOKEN: $T"{ "success": true, "totals": { "gallons_diesel": 12.4 }, "items": [ { "_id": "6651b2c4a1b2c3d4e5f60902", "operation": "accrue_units", "units": { "gallons_diesel": 12.4 }, "units_start": {}, "units_end": { "gallons_diesel": 12.4 }, "payment_id": null, "check_id": null, "restaurant_id": "64f0a1b2c3d4e5f607182930", "created_at": "2026-11-06 09:20:11" } ], "total": 1}Fix an entry in place instead of posting a correction. amount is that entry’s own change,
not the card’s total:
curl -X PUT https://dev-api.katalystos.com/v1/integration/loyalty/card/6650f0c2a1b2c3d4e5f60718/units/6651b2c4a1b2c3d4e5f60902/ \ -H "X-KATALYST-AUTH-TOKEN: $T" -H "Content-Type: application/json" \ -d '{"code": "gallons_diesel", "amount": 1.24}'Send new_code to move the entry to another code, amount to change it, or both. An amount of 0
is rejected; delete the entry instead:
curl -X DELETE https://dev-api.katalystos.com/v1/integration/loyalty/card/6650f0c2a1b2c3d4e5f60718/units/6651b2c4a1b2c3d4e5f60902/ \ -H "X-KATALYST-AUTH-TOKEN: $T"Deleting removes every code in that entry. Edits and deletes are refused if a total would go below zero; remove the later correction first.
WEDU enrollment
Section titled “WEDU enrollment”WEDU integrations have one more endpoint, which needs the integration.wedu permission.
POST /v1/integration/wedu/loyalty-user/ finds or creates the diner by email or phone, links them
to the restaurant, and creates their card if they do not have one yet:
{ "email": "ana@example.com", "phone": "5551234567", "first_name": "Ana", "last_name": "Diaz", "external_card_number": "WD-100234"}phone is exactly 10 digits. The response has the diner as user and the card as card.
Errors you will see
Section titled “Errors you will see”Message (in errors) |
Cause |
|---|---|
No loyalty program found for this restaurant. |
The restaurant has no loyalty program, or restaurant_id is wrong. |
Loyalty card not found. |
No card with that id or lookup value in this program. |
Multiple cards match... |
Look the card up by number. |
This card number is already in use. |
Another card in the program has the number. |
Punch card program not found. |
Wrong id, another program’s id, or the program is inactive. |
Units entry not found. |
The operation_id is not a units entry of this card. |
All of these are HTTP 200 with success: false. See Responses and errors.