Management API
Besides looking up members and registering transactions (Public API v1), /api/v1 lets you configure the programme without opening the back office: programmes and tiers, campaigns, the rewards catalogue, coupon batches and raffle results.
In the examples, $BASE is your platform's address and $KEY your API key (or an OAuth2 token).
Authentication and scopes
Every call carries Authorization: Bearer mk_.... The key belongs to a customer account and everything is scoped to that account: a resource from another account answers 404, never 403.
These scopes are explicit: a key with no scopes (the older, full-access ones) does not get them, and they have to be granted to the key expressly.
The Scopes selector in Administration › API Keys shows a different list (documents:read, credits:write, webhooks:write…) and does not accept free values. To grant the scopes on this page, use the administration API with an administrator session that holds the API Keys permission: POST /api/services/app/ApiKey/UpdateApiKey (or CreateApiKey for a new key) with the scopes field as a comma-separated string, for example "read,programs:read,programs:write". UpdateApiKey takes the whole key (id, name, isActive, limits…), not just scopes.
| Scope | Allows |
|---|---|
programs:read | Read programmes and tiers |
programs:write | Create, update and delete programmes and tiers |
campaigns:read | Read campaigns, their custom fields and participation rules |
campaigns:write | Create, update and delete campaigns; activate, pause; fields and rules |
rewards:read | Read the rewards catalogue |
rewards:write | Create, replace and delete rewards |
coupons:read | List coupons and their codes |
coupons:manage | Issue batches and invalidate coupons |
raffles:read | Read completed raffles and their winners |
read | Read the member attribute schema (in addition to the Public API reads) |
Scopes are matched exactly: campaigns:write does not include campaigns:read, and coupons:write (till redemption) does not let you issue coupons. Give a key only what it needs.
Each block also requires the feature to be included in the account's plan (points programme, tiers, campaigns, rewards, coupons; and Bulk coupon generator to issue batches). If it is not, the response is 403 with code: feature_disabled.
Common conventions
Pagination. Lists return { "items": [...], "nextCursor": "..." }. Send nextCursor as cursor to get the next page; it is null on the last one. limit goes from 1 to 200 (default 50; raffles: 1 to 100, default 20). A tampered cursor gives 400 with code: invalid_cursor.
Errors. The body always carries a stable code field; program against code, not the text. Codes are never renamed, but new ones may be added. Hourly limits and 429 are those of the key.
Idempotency. POST, PATCH and DELETE accept Idempotency-Key (see Public API). It is mandatory when issuing coupon batches (400 with code: idempotency_key_required if missing).
Enumerations. Programmes, tiers and campaigns use lowercase snake_case text (points, instant_win). Rewards and coupons use the name as is (Physical, PercentageDiscount). Handle unknown values gracefully: new ones may be added.
Partial updates (PATCH). Only the properties you send change. An explicit null clears the fields that allow it; on the rest it gives 400 validation_failed.
Programmes
An account has at most one live programme. Plan feature: points programme.
| Method and path | Scope | What it does |
|---|---|---|
GET /api/v1/programs?status=&cursor=&limit= | programs:read | List (by id) |
GET /api/v1/programs/{id} | programs:read | One programme |
POST /api/v1/programs | programs:write | Create (201) |
PATCH /api/v1/programs/{id} | programs:write | Partial update |
DELETE /api/v1/programs/{id} | programs:write | Delete (204) |
type values: points, cashback, stamps, product_cashback. status values: draft, active, paused, archived. Main fields: name, description, pointsCurrencyName, pointsToMoneyRate (money value of one point, 0 to 1), pointsExpirationDays, pendingPeriodDays, qualifyingPointsWindowMonths, tierGracePeriodDays, cashbackPercentage and modules (points, cashback, stamps, evidence, games, giftCards, courses, challenges, each true/false).
curl -s -X POST "$BASE/api/v1/programs" \
-H "Authorization: Bearer $KEY" -H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"name":"Points programme","type":"points","pointsCurrencyName":"points","pointsToMoneyRate":0.01}'
Validation is the same as in the back office (for example, the pending period must be shorter than points expiry). Specific errors:
| Status | code | When |
|---|---|---|
409 | program_already_exists | The account already has a programme |
409 | program_in_use | On delete: it still has tiers or members |
404 | program_not_found | It does not exist in your account |
422 | quota_exceeded | Exceeds your plan's programme limit |
Tiers
Plan feature: tiers.
| Method and path | Scope |
|---|---|
GET /api/v1/tiers?programId=&cursor=&limit= | programs:read |
GET /api/v1/tiers/{id} | programs:read |
POST /api/v1/tiers | programs:write |
PATCH /api/v1/tiers/{id} | programs:write |
DELETE /api/v1/tiers/{id} | programs:write |
To create one, programId and name are required. Fields: level, criteriaType (points_accumulated, total_spent, transaction_count), minPoints, maintenancePoints, pointsMultiplier, color, iconUrl, isDefault. A tier cannot move to another programme.
curl -s -X POST "$BASE/api/v1/tiers" \
-H "Authorization: Bearer $KEY" -H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"programId":7,"name":"Gold","level":3,"minPoints":1000,"maintenancePoints":800,"pointsMultiplier":1.5}'
Deleting a tier that members still hold gives 409 with code: tier_in_use.
Campaigns
You manage the campaign shell. Type-specific setup (games, prizes, winning moments, the landing builder) stays in the back office. Plan feature: campaigns.
| Method and path | Scope | Notes |
|---|---|---|
GET /api/v1/campaigns?status=&type= | campaigns:read | Most recent first |
GET /api/v1/campaigns/{id} | campaigns:read | |
POST /api/v1/campaigns | campaigns:write | 201; always starts as draft |
PATCH /api/v1/campaigns/{id} | campaigns:write | type and loyaltyProgramId do not change |
DELETE /api/v1/campaigns/{id} | campaigns:write | 204; drafts only |
POST /api/v1/campaigns/{id}/activate | campaigns:write | Becomes active (or scheduled if it has not started) |
POST /api/v1/campaigns/{id}/pause | campaigns:write | From active or scheduled |
GET / POST /api/v1/campaigns/{id}/custom-fields | read / write | Data that participation asks for |
PUT / DELETE /api/v1/campaigns/{id}/custom-fields/{fieldId} | campaigns:write | |
GET / PUT /api/v1/campaigns/{id}/participation-rules | read / write | Participation policy (policyJson, versioned) |
type: game, points_multiplier, bonus_points, challenge, cashback, instant_win, pin_code, ugc_contest, sports_prediction, raffle. status: draft, scheduled, active, paused, completed, cancelled, expired_deserted.
To create: name (up to 256 characters), type, startDate and endDate (the end must be after the start). Optional: slug, loyaltyProgramId, brandId, imageUrl, landing texts, pointsMultiplier (0.01 to 100), bonusPoints, cashbackPercentage (0 to 100), maxTotalEntries, maxPrizesPerParticipant, minAgeYears and budget. In PATCH, an empty text clears the text and 0 clears a limit or amount.
curl -s -X POST "$BASE/api/v1/campaigns" \
-H "Authorization: Bearer $KEY" -H "Idempotency-Key: create-summer-2027" \
-H "Content-Type: application/json" \
-d '{"name":"Summer bonus","type":"bonus_points","startDate":"2027-06-01","endDate":"2027-08-31","bonusPoints":100}'
curl -s -X POST "$BASE/api/v1/campaigns/17/activate" -H "Authorization: Bearer $KEY"
| Status | code | When |
|---|---|---|
400 | invalid_request | Values out of range or required ones missing |
403 | feature_disabled | Campaigns, or cashback when the type/percentage needs it, not included in the plan |
404 | campaign_not_found | It does not exist in your account |
409 | campaign_not_draft | Deleting a campaign that is not a draft (pause it instead) |
409 | invalid_status_transition | Activating or pausing from a state that does not allow it |
409 | budget_paused | Paused by its budget: raise it and reactivate it from the back office |
409 | end_date_frozen | Changing the end date of a campaign whose raffle was already drawn, or of a closed campaign |
409 | slug_taken | The slug is already in use |
422 | cannot_go_live | activate refused (window over, invalid rules, instant-win without winning moments); the message says why |
422 | loyalty_program_not_found, brand_not_found | The reference is not in your account |
422 | quota_exceeded | Exceeds your plan's campaign limit |
Rewards
The catalogue redeemable with points. Plan feature: rewards.
| Method and path | Scope |
|---|---|
GET /api/v1/rewards?programId=&campaignId=&status=&category= | rewards:read |
GET /api/v1/rewards/{id} | rewards:read |
POST /api/v1/rewards | rewards:write |
PUT /api/v1/rewards/{id} | rewards:write |
DELETE /api/v1/rewards/{id} | rewards:write |
rewardType: Physical, Digital, Experience, Discount, Cashback. fulfillmentType: Digital, Ship, InStore, External. status when writing: Available or Disabled; OutOfStock is derived by the platform from the stock.
curl -s -X POST "$BASE/api/v1/rewards" \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"programId":5,"name":"Free coffee","rewardType":"Physical","pointsCost":200,"stock":500,"fulfillmentType":"InStore"}'
programId,nameandrewardTypeare required.imageUrlmust be an absolutehttp(s)URL.PUTreplaces the fields: anything you leave out takes its default.stockis only read on create; afterwards it moves with redemptions and the back-office stock adjustment.- Gift-card fulfilment and mixed points-plus-cash settings can only be edited in the back office.
DELETEis a soft delete (204): past redemptions keep the reference.- Errors:
reward_not_found,program_not_found,campaign_not_found(404),feature_disabled(403) andvalidation_failed(400).
Coupons
To redeem at the till, use the Public API endpoints. This section manages the coupons that exist.
| Method and path | Scope |
|---|---|
GET /api/v1/coupons?programId=&campaignId=&batchId=&status=&memberId=&code= | coupons:read |
GET /api/v1/coupons/{id} | coupons:read |
POST /api/v1/coupons/batches | coupons:manage |
POST /api/v1/coupons/{id}/invalidate | coupons:manage |
POST /api/v1/coupons/batches/{batchId}/invalidate | coupons:manage |
Statuses: Active, Used, Expired, Cancelled. Types (couponType): FixedDiscount, PercentageDiscount, FreeProduct, FreeShipping, Custom.
Issue a batch
curl -s -X POST "$BASE/api/v1/coupons/batches" \
-H "Authorization: Bearer $KEY" -H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"programId":5,"count":500,"couponType":"PercentageDiscount","discountValue":10,"prefix":"SUMMER","codeLength":10,"maxUses":1,"validUntil":"2027-09-30"}'
# 201 {"batchId":"3F9A1C7E0B42","issued":500,"coupons":[{"id":812,"code":"SUMMER-K7Q2M9XH4T"}, ...]}
- Up to 1,000 coupons per batch. Codes are random and unique.
codeFormat:Alphanumeric(default),NumericorAlpha.codeLengthfrom 6 to 20 (10 by default). If it is too short forcountit is refused so codes stay hard to guess.prefix: up to 10 letters or digits.maxUsesempty = unlimited uses;1for single-use codes.maxUsesPerMember,pointsCost,minTierLevel,validFromandvalidUntilare optional.- Coupons in a batch do not appear in the member portal unless you send
isListed: true. - A retry with the same
Idempotency-Keyand the same body returns the same batch. - Errors:
409 code_space_exhausted(not enough free codes: lengthencodeLengthor change the prefix),409 conflict(clash with a simultaneous request: retry),404program_not_found/campaign_not_found,403 feature_disabled.
Invalidate
An Active coupon becomes Cancelled and can no longer be redeemed. Repeating the call is harmless (invalidated: 0). A coupon already Used or Expired gives 409 with code: coupon_not_active. Invalidating a batch answers { "invalidated": N }; an unknown batch gives 404 with code: batch_not_found.
Raffles
GET /api/v1/campaigns/{id}/raffles (scope raffles:read) returns the campaign's completed raffles, oldest first, each with its winners and reserves. It does not depend on the public winners page being enabled.
curl -s "$BASE/api/v1/campaigns/17/raffles?limit=20" -H "Authorization: Bearer $KEY"
Each raffle includes drawId, drawnAt, scope, storeId, slotKey, the requested and drawn counts, eligiblePoolSize and the audit hashes printed on the certificate (poolHashSha256, outputHashSha256, seedSha256; never the seed itself). Each winner carries role, position, status, prizeId, notified and only your own references (externalParticipantRef, memberExternalId, claimId) plus the masked name and email (maskedName, maskedEmail). There is no contact data. A campaign with no raffles returns an empty items.
Errors: 404 campaign_not_found, 403 feature_disabled (campaigns), 400 invalid_cursor / invalid_request.
Member attribute schema
GET /api/v1/member-attributes (scope read) returns the custom attributes your account has defined for members, ordered by displayOrder, so you know which keys and types a member's attributes object accepts.
curl -s "$BASE/api/v1/member-attributes" -H "Authorization: Bearer $KEY"
Each item carries key, displayName, description, dataType, isRequired, allowedValues, displayOrder, isActive and creationTime. dataType is numeric: 1 text, 2 number, 3 boolean, 4 date (yyyy-MM-dd) and 5 select (the value must be in allowedValues). Only active attributes are returned. Definitions are managed in the back office, not through this API; key and dataType do not change once a definition exists.
Next steps
- OAuth2: short-lived tokens instead of sending the key.
- Admin scopes: call the rest of the back office by API.
- Webhook events: hear about participations, raffles and coupons.
- Test mode: test without affecting real members.
- Postman collection