Endpoints
All paths are relative to https://app.mekante.com/api/v1/public. Every request requires an Authorization: Bearer sk_live_… header (Authentication). UUIDs (id) in the examples below are abbreviated for readability.
| Method | Path | Description |
|---|---|---|
GET | /me | Business, loyalty program, and location bound to the key |
POST | /scan | Scan a customer's QR token |
GET | /scans/:id | Re-read an active, unconsumed scan |
POST | /earn/preview | Preview points/stamps for a purchase without saving |
POST | /earn | Award points and/or stamps |
POST | /redeem | Redeem a reward |
POST | /ledger/:id/void | Void a recent ledger entry |
GET | /ledger/recent | Last 20 transactions made by this key |
GET | /ledger/mine | Paginated history of transactions made by this key |
GET /me
Returns the business, loyalty program, and locations associated with the API key. Useful for health-checking your credentials and reading program configuration.
curl https://app.mekante.com/api/v1/public/me \
-H "Authorization: Bearer sk_live_EXAMPLE_KEY"
A 200 response contains role, tenant, program, and locations. program.type is points, stamps, or hybrid.
POST /scan
Verifies and consumes a customer's 30-second QR token and creates a 5-minute scanId.
Request body
| Field | Type | Description |
|---|---|---|
token | string, 10–1000 chars | Raw QR code content |
locationId | uuid, optional | Location ID. Optional because an API key is bound to one branch |
curl -X POST https://app.mekante.com/api/v1/public/scan \
-H "Authorization: Bearer sk_live_EXAMPLE_KEY" \
-H "Content-Type: application/json" \
-d '{ "token": "QR_TOKEN_CONTENT" }'
Response 200
{
"scanId": "e227e15a-…",
"intent": "earn",
"member": {
"id": "db997e9d-…",
"name": "Ayşe Yılmaz",
"phone": "+90 559 *** ** 39",
"balance": 0,
"stampBalance": 0,
"tier": null
},
"program": { "type": "points", "stampsPerReward": null, "maxStampsPerTransaction": 10 },
"reward": null
}
intent is either earn (ordinary customer card QR) or redeem (reward-specific QR). When intent is redeem, reward contains { id, name, costPoints, costCurrency }, where costPoints is the price in costCurrency (points or stamps). Customer phone numbers are always masked.
program.type indicates how the business's loyalty program works:
| Type | What gets awarded |
|---|---|
points | Points calculated from purchase amount. amount is required |
stamps | Stamps. No purchase amount is needed; pass stamps for how many stamps to award (defaults to 1) |
hybrid | Both balances. Awards stamps stamps, and if amount is also provided, awards points from that amount |
rewards is populated when scanning the customer's standard QR code (and empty when the customer scanned a single-reward QR, where reward is populated instead). Each item in rewards has { id, name, type, costPoints, costCurrency, eligible, reason, missing }: eligible indicates whether the member can redeem it right now, reason explains why not (INSUFFICIENT_BALANCE, OUT_OF_STOCK, LIMIT_REACHED, EXPIRED, NOT_STARTED, INACTIVE), and missing is how many more points or stamps are needed. Sorted cheapest first.
member.balance is the point balance and member.stampBalance is the stamp balance. program.maxStampsPerTransaction is the maximum number of stamps that can be awarded in a single visit.
Possible errors: 400 QR_INVALID, 400 QR_EXPIRED, 409 QR_ALREADY_USED, 404 MEMBERSHIP_NOT_FOUND, 402 TRIAL_EXPIRED, 402 SUBSCRIPTION_SUSPENDED.
GET /scans/:id
Returns an unconsumed, non-expired scan with the exact same payload shape as POST /scan. Useful for recovering state after a POS screen refresh. Returns 409 SCAN_INVALID if already consumed or expired.
POST /earn/preview
Calculates how many points and stamps a visit will award without writing to the database. Does not require an Idempotency-Key.
Request body: scanId (uuid); amount (integer kuruş, positive; required for points, optional for hybrid, ignored for stamps); stamps (integer; used for stamps and hybrid programs, defaults to 1).
{ "scanId": "e227e15a-…", "amount": 15000 }
Response 200
{
"points": 150,
"stamps": 0,
"multiplier": 1,
"multiplierCampaignId": null,
"bonuses": [],
"total": 150,
"stampsTotal": 0,
"expiresAt": "2027-10-23T09:57:08.237Z",
"balanceAfter": 150,
"stampBalanceAfter": 0
}
multiplier is the active tier or campaign multiplier (multipliers never stack; the highest wins) and applies to points only. total is the total points to be awarded on this visit and stampsTotal is the total stamps (including welcome/birthday bonuses, which follow the program's main currency). expiresAt is when the earned points/stamps expire.
POST /earn
Awards points and/or stamps. The Idempotency-Key header is required (Till flow).
Request body
| Field | Type | Description |
|---|---|---|
scanId | uuid | From POST /scan |
amount | integer, kuruş | Purchase total in kuruş. Required for points, optional for hybrid (awards points when given), ignored for stamps |
stamps | integer, optional | Number of stamps to award (stamps and hybrid programs, defaults to 1, up to the business's per-visit cap). Pass 0 in hybrid for points-only |
receiptNo | string, 1–40, optional | POS receipt number. Prevents recording the same receipt twice |
curl -X POST https://app.mekante.com/api/v1/public/earn \
-H "Authorization: Bearer sk_live_EXAMPLE_KEY" \
-H "Idempotency-Key: pos-2026-000123" \
-H "Content-Type: application/json" \
-d '{ "scanId": "e227e15a-…", "amount": 15000, "receiptNo": "A-1042" }'
Response 201 (200 with "replayed": true on idempotent retry)
{
"replayed": false,
"entries": [{ "id": "3f99048a-…", "type": "earn", "points": 150, "stamps": 0 }],
"total": 150,
"stamps": 0,
"balance": 150,
"stampBalance": 0,
"membershipId": "db997e9d-…"
}
entries[].id is the ledger entry ID used if you need to void the transaction. total and stamps are the points and stamps awarded in this transaction; balance and stampBalance are the updated member balances.
Possible errors: 400 (missing header or invalid body), 409 SCAN_INVALID, 404 SCAN_NOT_FOUND, 409 DUPLICATE_RECEIPT, 409 DUPLICATE_REQUEST, 422 AMOUNT_TOO_HIGH, 429 DAILY_EARN_LIMIT, 429 CASHIER_DAILY_LIMIT, 400 INVALID_AMOUNT, 400 INVALID_STAMPS, 422 TOO_MANY_STAMPS, 400 NOTHING_TO_EARN (neither stamps nor amount provided in a hybrid program).
POST /redeem
Redeems a reward. The Idempotency-Key header is required.
Request body: scanId (uuid) and rewardId (uuid, optional). If you scanned the customer's ordinary QR code, rewardId is required (400 REWARD_REQUIRED). If the customer presented a reward-specific QR code, the reward is embedded in the scan; if you also pass rewardId, it must match (400 REWARD_MISMATCH).
Response 201
{
"replayed": false,
"entries": [{ "id": "922b40fd-…", "type": "redeem", "points": -100, "stamps": 0 }],
"total": -100,
"stamps": 0,
"balance": 250,
"stampBalance": 3,
"membershipId": "db997e9d-…",
"code": "WZ86BV",
"reward": {
"id": "7d5cb889-…",
"name": "Bedava kahve",
"costPoints": 100,
"costCurrency": "points"
}
}
code is the 6-character redemption code shown to the customer and cashier.
Possible errors: 400 REWARD_REQUIRED, 400 REWARD_MISMATCH, 429 CASHIER_DAILY_LIMIT, 409 INSUFFICIENT_BALANCE, 409 REWARD_OUT_OF_STOCK, 409 REWARD_INACTIVE, 409 REWARD_EXPIRED, 409 REWARD_NOT_STARTED, 409 REWARD_LIMIT_REACHED, 404 REWARD_NOT_FOUND, 409 SCAN_INVALID, 409 DUPLICATE_REQUEST.
POST /ledger/:id/void
:id is the ID of an earn or redeem ledger entry (entries[].id). No request body is needed.
curl -X POST https://app.mekante.com/api/v1/public/ledger/922b40fd-…/void \
-H "Authorization: Bearer sk_live_EXAMPLE_KEY"
Response 200: reverses the original entry by appending a reverse ledger entry.
{
"replayed": false,
"entries": [{ "id": "fab26888-…", "type": "reverse", "points": 100, "stamps": 0 }],
"total": 100,
"stamps": 0,
"balance": 350,
"stampBalance": 3,
"membershipId": "db997e9d-…"
}
Possible errors: 404 ENTRY_NOT_FOUND, 409 ALREADY_VOIDED, 409 POINTS_ALREADY_SPENT, 409 NOT_VOIDABLE, 403 FORBIDDEN (another cashier's entry), 403 VOID_WINDOW_PASSED (older than 15 minutes).
GET /ledger/recent
Returns an array of the 20 most recent earn and redeem transactions performed by this API key, newest first.
[
{
"id": "3f99048a-…",
"type": "earn",
"points": 150,
"amount": 15000,
"createdAt": "2026-10-23T09:57:08.246Z",
"voided": false,
"member": "Ayşe Yılmaz"
}
]
GET /ledger/mine
Returns a cursor-paginated list of transactions performed by this API key.
Query parameters: limit (1–100, default 50), cursor (nextCursor from the previous page).
{
"items": [
{
"id": "3f99048a-…",
"type": "earn",
"points": 150,
"amount": 15000,
"createdAt": "2026-10-23T09:57:08.246Z",
"voided": false,
"member": "Ayşe Yılmaz"
}
],
"nextCursor": null
}
When nextCursor is null, you have reached the last page.