Public API v1
A REST API to integrate a POS, an online shop or a CRM with your loyalty programme. All routes live under /api/v1 and return JSON.
You need an API key from your account with the right scopes. Every call carries Authorization: Bearer mk_.... Keys from the host account do not work (403).
To create and manage programmes, tiers, campaigns, rewards and coupons, see the Management API. If you prefer short-lived tokens, see OAuth2.
In the examples, $BASE is your platform's address and $KEY is your key.
Overview
| Method and route | Scope | What it does |
|---|---|---|
GET /api/v1/members/lookup | read | Find a member by phone, email or card |
GET /api/v1/members/{memberId}/balance | read | Balance and tier |
GET /api/v1/members/{memberId}/transactions | read | Transaction history |
POST /api/v1/transactions/purchase | transactions:write | Register a purchase and earn points |
POST /api/v1/transactions/action | transactions:write | Earn points for an action |
POST /api/v1/transactions/redeem | transactions:write | Burn points |
POST /api/v1/transactions/adjust | transactions:write | Manual adjustment (add only) |
GET /api/v1/coupons/validate | read | Check whether a coupon can be used |
POST /api/v1/coupons/redeem | coupons:write | Redeem a coupon at the till |
Every route that takes a programId requires the programme and the member to belong to the key's account.
Interactive reference
The platform serves the public-v1 OpenAPI reference at /docs/api-v1, using an open-source viewer (Swagger UI) with the Try it out button. Click Authorize, paste a test key and run the calls.
"Try it out" only sends calls made with mk_test_... keys, which authenticate only while the account is in test mode. With a live key (mk_...) the page sends neither the key nor the call. Every response carries X-IncentIA-Test-Mode: true.
Members
Look up a member
curl -s "$BASE/api/v1/members/lookup?programId=1&phone=%2B34600111222" \
-H "Authorization: Bearer $KEY"
| Parameter | Required | Notes |
|---|---|---|
programId | Yes | Loyalty programme id |
phone, email, cardNumber | One of the three | If you send several, the order is: phone, email, card |
200 response:
{
"memberId": 42, "firstName": "Ana", "lastName": "Ruiz",
"email": "ana@example.com", "phone": "+34600111222", "status": "Active",
"balance": { "current": 320, "totalEarned": 900, "totalRedeemed": 580,
"tierName": "Silver", "tierLevel": 2 }
}
Returns 400 if you send no identifier and 404 if the member does not exist.
Balance
GET /api/v1/members/{memberId}/balance?programId=1 returns memberId, programId, currentBalance, totalEarned, totalRedeemed, totalExpired, tierName and tierLevel.
History
GET /api/v1/members/{memberId}/transactions?programId=1&limit=20
limit ranges from 1 to 200 (default 20). Transactions are returned newest first. To page, send cursor with the nextCursor from the previous response; nextCursor is null on the last page. offset only works when you send no cursor and is legacy.
{ "total": 57, "items": [
{ "id": 9001, "type": "Earn", "points": 50, "description": "Purchase",
"externalReference": "ORDER-12345", "createdAt": "2026-10-20T10:15:00Z" } ] }
Point transactions
All four endpoints accept externalReference, an identifier of your own (for example a receipt number). If you resend the same reference the transaction is not duplicated, so it is safe to retry after a network failure.
Purchase
curl -s -X POST "$BASE/api/v1/transactions/purchase" \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"memberId":42,"programId":1,"amount":49.99,"externalReference":"ORDER-12345"}'
| Field | Required | Notes |
|---|---|---|
memberId, programId | Yes | |
amount | Yes | Greater than 0. A refund sent as 0 or negative returns 400 |
externalReference | No | Idempotency key |
200 response: pointsEarned, currentBalance, tierName and transactions (a list with id, type, points, description). Active campaigns may add bonus transactions.
Action
POST /api/v1/transactions/action with memberId, programId, actionType and, optionally, customEventCode and externalReference.
actionType accepts: Registration (1), Birthday (2), Review (3), CheckIn (4), SocialShare (5), Referral (6), ProfileComplete (7) and Custom (99). Response: pointsEarned, currentBalance and transactionId. If no rule rewards the action, it returns pointsEarned: 0 and transactionId: null.
Redeem points
POST /api/v1/transactions/redeem with memberId, programId, points and, optionally, description and externalReference. Response: pointsRedeemed, currentBalance and transactionId. With insufficient balance it returns 400 (Insufficient points.).
Manual adjustment
POST /api/v1/transactions/adjust with memberId, programId, points (an integer greater than 0), description and externalReference. It is a manual correction: campaign multipliers do not apply. Response: pointsAdjusted, currentBalance and transactionId.
Coupons
Validate
curl -s "$BASE/api/v1/coupons/validate?code=SUMMER20&memberId=42" \
-H "Authorization: Bearer $KEY"
code is required. memberId is optional, but without it a personal coupon, or one with a tier or points cost, is reported as not valid. The response includes valid and the reasons: isExpired, isMaxUsed, alreadyUsedByMember, ownerMismatch, tierTooLow, insufficientPoints, plus couponType, discountValue, pointsCost, minTierLevel, validFrom and validUntil. An unknown code returns 404 with valid: false.
Redeem
curl -s -X POST "$BASE/api/v1/coupons/redeem" \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"code":"SUMMER20","memberId":42,"externalReference":"TICKET-778"}'
200 response: success, couponType, discountValue, pointsSpent and usageId. With the same externalReference for the same member, a retry returns the original response without spending another use. If that reference already redeemed the coupon for a different member, you get 409.
Idempotency
Every POST, PATCH or DELETE under /api/v1 accepts an optional Idempotency-Key header (1-128 printable characters; use one UUID per operation). It lets you retry without creating duplicates:
- Same value, same request (method, path, query and body; for
multipart/form-datathe fields and files are compared, not the boundary, so you can rebuild the request when retrying) within 24 h: you get the original response back, with theIdempotent-Replayed: trueheader. The operation is not run again. - Same value, different request:
409withcode: idempotency_key_reused. - Same value while the first request is still running:
409withcode: idempotency_request_in_progressandRetry-After: 1.
5xx, 401, 403, 408, 409, 425 and 429 responses are not stored, so you can retry with the same value. Each API key has its own key space. The header is mandatory on POST /api/v1/claims, POST /api/v1/participations, POST /api/v1/participations/{id}/play, POST /api/v1/coupons/batches, POST /api/v1/members/imports and POST /api/v1/jobs/import and jobs/export.
Error codes
| Code | When |
|---|---|
400 | Invalid data, coupon inactive, expired or used up, insufficient points, tier too low |
401 | Key missing or invalid |
403 | The key lacks the scope, belongs to the host account, or the member is not in your account |
404 | Member, programme or coupon not found (deleted members too) |
409 | externalReference already used with another member, or Idempotency-Key reused or in progress (see Idempotency) |
429 | You exceeded the key's burst, per-minute or per-hour limit, or the monthly quota. Retry-After is the seconds until that window resets |
The body is application/problem+json with a stable code (bad_request, not_found, conflict, rate_limited, idempotency_key_reused, invalid_idempotency_key, business_rule_violated, ...); the legacy text remains in error.
Related
- Management API: programmes, tiers, campaigns, rewards, coupons and raffles.
- Webhooks: get notified about points, redemptions, coupons, participations and raffles.
- Postman collection and Test mode.
- Service status, SLA and maintenance.
- JavaScript SDK: wraps these routes for Node or the browser.