Cardy docs

Changelog

Every partner-visible change to the Loyalty API, newest first. Changes within /v1 are additive: we do not remove fields, rename error codes, change types, or make optional fields required. If we ever need to break something it will be /v2, running alongside /v1 — never a deadline handed to you.

2026-10-21

Pull a day's transactions for your accounting system. GET /transactions?from=&to= lists every transaction in your store whose business time falls in the window — not only the ones your system sent, but also adjustments your staff make in the Cardy dashboard, which your own records cannot contain. Cursor-paginated, newest first, up to 100 per page; from is inclusive and to exclusive, so consecutive days tile without overlap. The receipt lookup on the same path is unchanged and still scoped to your own references.

If you already poll GET /transactions?external_ref=, nothing changes except that the response now carries next_cursor (always null for a receipt lookup) beside data. A client that only reads data keeps working.

Every transaction now says where it came from, and what it reversed. channel is pos, ecommerce, cardy (an action in the Cardy dashboard or scanner app by your staff), manual (an adjustment by Cardy support on your behalf) or internal (a correction written by the nightly reconciliation). reverses_transaction_id is set on a reverse and names the transaction it undid, so a refund pairs with its sale without a second lookup; it is null everywhere else.

No action needed. Both fields are additive and present on every read.

2026-08-29

Scanning a card now identifies the customer holding it. loyalty_id — the serial your scanner reads off a card — has always been part of identify, and in the sandbox it resolved to nobody: every scan answered CUSTOMER_NOT_FOUND while the same customer's phone number worked. It resolves now, for any card, including ones issued before you integrated.

If you built against the phone lookup because the scan did not work, switch. The scan is what your cashiers use, and it is the identifier we trust over the others when two of them disagree.

You now get a test card, not just a test phone number. Your test card shows a real sandbox card's serial as a QR you can scan off the screen or print for the drawer. The quickstart starts there, and certification now checks that a scan and a phone lookup return the same customer.

No action needed, but the new Level 2 check runs on your next certification run. If your integration only ever identified by phone, it will pass — the check is on our resolution, not on your input.

allowed_operations now means "this will work". Four card types used to advertise redeem_points and then refuse it. If you were defensively handling a refusal on an operation we had listed, you no longer need to: every operation in the list is one the card can perform. Cards that hold money advertise redeem_amount instead, and cards that hold no spendable balance advertise no redemption at all.

No action needed. If you were suppressing buttons that we listed but that failed in practice, you can stop.

Back-dated sales beyond the offline window are now refused. occurred_at older than the store's offline window (24 hours by default) now answers VALIDATION_ERROR with details: ["occurred_at"]. It was previously accepted silently, which let a mis-configured till post sales into periods a store had already reconciled.

Action needed only if you back-date by more than a day. Normal offline batching is unaffected.

Subscription and multipass cards now report their points balance. balances.points was null on these cards and is now a number, so redemption against them works. Nothing changed shape — a field that was always present stopped being empty.


Entries begin here. Earlier changes predate the published API and are not listed; if you integrated before this date, nothing in the list above requires work beyond what each entry states.

Cardy Loyalty API Questions your integration cannot answer belong in an email, not a retry loop.