Skip to main content

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.

Before you start

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 routeScopeWhat it does
GET /api/v1/members/lookupreadFind a member by phone, email or card
GET /api/v1/members/{memberId}/balancereadBalance and tier
GET /api/v1/members/{memberId}/transactionsreadTransaction history
POST /api/v1/transactions/purchasetransactions:writeRegister a purchase and earn points
POST /api/v1/transactions/actiontransactions:writeEarn points for an action
POST /api/v1/transactions/redeemtransactions:writeBurn points
POST /api/v1/transactions/adjusttransactions:writeManual adjustment (add only)
GET /api/v1/coupons/validatereadCheck whether a coupon can be used
POST /api/v1/coupons/redeemcoupons:writeRedeem 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.

Test keys only

"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"
ParameterRequiredNotes
programIdYesLoyalty programme id
phone, email, cardNumberOne of the threeIf 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"}'
FieldRequiredNotes
memberId, programIdYes
amountYesGreater than 0. A refund sent as 0 or negative returns 400
externalReferenceNoIdempotency 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-data the 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 the Idempotent-Replayed: true header. The operation is not run again.
  • Same value, different request: 409 with code: idempotency_key_reused.
  • Same value while the first request is still running: 409 with code: idempotency_request_in_progress and Retry-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​

CodeWhen
400Invalid data, coupon inactive, expired or used up, insufficient points, tier too low
401Key missing or invalid
403The key lacks the scope, belongs to the host account, or the member is not in your account
404Member, programme or coupon not found (deleted members too)
409externalReference already used with another member, or Idempotency-Key reused or in progress (see Idempotency)
429You 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.