Quickstart — your first points in 15 minutes
By the end of this page you will have awarded points on a real sandbox card and seen them land. Everything here is copy-paste runnable.
What you need
Two things, both from us:
| Your test key | Looks like cardy_pk_test_…. It only ever reaches the sandbox — there is no way for a test key to touch live customer balances. |
| A test customer | A phone number we have already set up with cards. The examples below use 96599901001. |
The sandbox lives at:
https://connect-staging.getcardy.net/api/v1
Every request needs your key in the Authorization header. Set it once:
export CARDY_KEY="cardy_pk_test_replace_me"
export CARDY_URL="https://connect-staging.getcardy.net/api/v1"
1. Find the customer
This is what your till does when a customer presents their phone number or scans their card.
curl -s -X POST "$CARDY_URL/customers/identify" -H "Authorization: Bearer $CARDY_KEY" -H "Content-Type: application/json" -d '{"phone":"96599901001"}'
You get the customer and every card they hold:
{
"customer_id": "01a03fca-ffa8-70b4-b850-428496841903",
"cards": [
{
"card_id": "947706-732-680",
"template_name": "Demo Restaurant (points per KWD)",
"balances": { "points": 99, "stamps": null, "visits": null },
"allowed_operations": ["earn_amount", "redeem_points", "redeem_reward"]
}
]
}
allowed_operations is the field to build your buttons from. It lists what
this card can actually do. If redeem_points is not in the list, do not show a
redeem button — the card cannot do it, and the call would be refused.
Balances are null for dimensions a card does not have. A stamp card has no
points; that is different from having zero points.
2. Award points for a sale
curl -s -X POST "$CARDY_URL/transactions/earn" -H "Authorization: Bearer $CARDY_KEY" -H "Content-Type: application/json" -d '{"identifier":{"phone":"96599901001"},"external_ref":"receipt-1001","amount_minor":12500,"currency":"KWD"}'
{ "transaction_id": "cmtc1kl6w0000qs01bt4prksp", "status": "pending", "duplicate": false }
Two fields deserve attention.
external_ref is your receipt number. Send your own — whatever identifies
this sale in your system. It is how you look the transaction up later, how you
refund it, and how we stop a retry from paying twice.
amount_minor is an integer. KWD has three decimals, so 12.500 KWD is
12500. Never send a decimal; never assume two places.
status: "pending" means we have accepted the sale and are applying it. The
customer's balance updates within a second or two. Your till does not need to
wait — the sale is already recorded.
3. Prove it landed
Look the sale up by the receipt number you sent:
curl -s "$CARDY_URL/transactions?external_ref=receipt-1001" -H "Authorization: Bearer $CARDY_KEY"
{
"data": [
{
"transaction_id": "cmtc1kl6w0000qs01bt4prksp",
"intent": "earn",
"status": "success",
"external_ref": "receipt-1001",
"card_id": "144389-894-749",
"amount_minor": 12500,
"currency": "KWD",
"points": null
}
]
}
status has moved from pending to success — the sale has been applied.
Note card_id: you did not name a card, so we picked the one this sale should
land on. And points is null while amount_minor is set, because we record
what you sent us. The card's own balance moved in whatever unit that card keeps
— run step 1 again and you will see it.
If you want the sale on a particular card, send card_id from the identify
response.
4. Now retry it
Send the exact same earn request from step 2 a second time.
{ "transaction_id": "cmtc1kl6w0000qs01bt4prksp", "status": "success", "duplicate": true }
Same transaction, duplicate: true, and the customer was not paid twice.
This is the most important thing to understand about this API. Your till will
lose connectivity mid-sale, and it will not know whether we got the request.
The answer is always the same: send it again with the same external_ref.
It cannot double-award. You never need to reconcile, ask us, or write logic to
guess what happened.
The one way to break this is to generate a new external_ref on retry. Don't.
The receipt number is the same receipt number.
Where to go next
- Error handling — every code, what the cashier should see, and what your code should do.
- Certification — what we check before you go live, and how to run those checks yourself.
- Postman collection — every endpoint, ready to import.