Loyalty Programs
Manage of promotions with Loyalty Program type and rewards type.
All JSON response field names in this API are camelCase (generated automatically from the underlying C# model), with a single deliberate exception —
APIRequireIssyTermsAcceptance/apiRequireIssyTermsAcceptance, casing differs by endpoint, see below. Request bodies use PascalCase (matching the API's internal model) as shown throughout this doc.
termsAndConditionsis an HTML string, written with a rich-text editor in issy's backoffice — e.g."<p>This is the space where the Terms and Conditions of the <b><u>Hennessy Bahamas Loyalty Program</u></b> will be displayed.</p>". Render it as sanitized HTML (e.g. DOMPurify), not as plain text. It'snullwhen nothing is loaded. To build the full consent UI (privacy policy, brand opt-in, issy's legal pages) use Get Terms display — see Showing the consent UI.
Available Loyalty Programs
Get list of available Loyalty Programs. The response is a bare JSON array (not wrapped in an envelope).
GET /loyaltyprograms
RESPONSE (200)
[
{
"id": "{promotion id}",
"tenantId": "{tenant id}",
"name": "{promotion name}",
"tags": "{array of strings - free-form labels assigned to the promotion in the backoffice (e.g. ["summer", "vip"]). Trimmed, case-insensitively de-duplicated, returned in the order they were saved. Always an array, [] when there are no tags}",
"description": "{promotion description}",
"code": "{promotion unique code}",
"enabled": "{promotion enabled}",
"mainImageUrl": "{promotion image url}",
"type": "{promotion type}",
"prizeType": "{promotion prize type}",
"requirePOPCode": "{whether participating requires a POP code - see Choosing a participation flow in Participations}",
"mechanicType": "{promotion mechanic type}",
"startDate": "{date when promotion starts}",
"endDate": "{date when promotion ends}",
"fraudPreventionModuleActivation": "{is active the fraud prevention module}",
"termsAndConditions": "{HTML string - promotion terms and conditions, see note below. Can be null}",
"policyPrivacyUrl": "{promotion privacy policy url}",
"apiRequireIssyTermsAcceptance": "{true/false, never null - see Terms & Conditions Acceptance in Get Started. camelCase here (list endpoint)}",
"requireAgegateConfiguration": "{true/false, never null - whether the API enforces a minimum age on registration and participation - see Age Gate in Get Started}",
"minimumAge": "{integer, never null - minimum age (in years) the user must have. 0 when not configured. Only enforced when requireAgegateConfiguration is true and minimumAge > 0}",
"endPromotionDesc": "{message to show when the participation process finishes}"
}
]
An empty array ([]) means the tenant has no available loyalty programs — not an error.
Error responses:
| HTTP | errorCode | Cause |
|---|---|---|
| 404 | PROMOTION_NOT_FOUND | The search returned null (unexpected — normally an empty array is returned instead). |
| 401 / 406 | INVALID_HEADER_CONTENT | Missing/invalid issy-token or issy-tenant-id. |
| 500 | INTERNAL_ERROR | Unexpected server error. |
Get Loyalty Program
Get an individual Loyalty Program's full information, including its participation form and product list.
GET /loyaltyprograms/{promotion id}
RESPONSE (200)
{
"id": "{promotion id}",
"tenantId": "{tenant id}",
"name": "{promotion name}",
"tags": "{array of strings - free-form labels assigned to the promotion in the backoffice (e.g. ["summer", "vip"]). Trimmed, case-insensitively de-duplicated, returned in the order they were saved. Always an array, [] when there are no tags}",
"description": "{promotion description}",
"code": "{promotion unique code}",
"enabled": "{promotion enabled}",
"mainImageUrl": "{promotion image url}",
"type": "{promotion type}",
"prizeType": "{promotion prize type}",
"requirePOPCode": "{whether participating requires a POP code - see Choosing a participation flow in Participations}",
"requireProofOfPurchase": "{whether participating requires an invoice/receipt image - only present in this detail response, not in the list above - see Choosing a participation flow in Participations}",
"proofOfPurchaseType": "{0 = invoice/receipt OCR, 1 = photo (AI rule evaluation) - independent of requireProofOfPurchase/requirePOPCode, a loyalty program can accept code, receipt and photo participation all at once - see Choosing a participation flow in Participations}",
"photoParticipationCredits": "{fixed credits granted per approved photo participation - only present when proofOfPurchaseType is 1}",
"mechanicType": "{promotion mechanic type}",
"startDate": "{date when promotion starts}",
"endDate": "{date when promotion ends}",
"fraudPreventionModuleActivation": "{is active the fraud prevention module}",
"termsAndConditions": "{HTML string - promotion terms and conditions, see note below. Can be null}",
"policyPrivacyUrl": "{promotion privacy policy url}",
"APIRequireIssyTermsAcceptance": "{true/false, never null - see Terms & Conditions Acceptance in Get Started. PascalCase here (detail endpoint, kept for backwards compatibility)}",
"requireAgegateConfiguration": "{true/false, never null - whether the API enforces a minimum age on registration and participation - see Age Gate in Get Started}",
"minimumAge": "{integer, never null - minimum age (in years) the user must have. 0 when not configured. Only enforced when requireAgegateConfiguration is true and minimumAge > 0}",
"endPromotionDesc": "{message to show when the participation process finishes ('End of Participation' message)}",
"officialReceiptName": "{title for the invoice/receipt capture screen ('Purchase Validation' title)}",
"photoLabel": "{title for the photo participation capture screen - used when proofOfPurchaseType is 1, null when not set}",
"rankingResetType": "{ranking window: 0 = None, 1 = Daily, 2 = Weekly, 3 = Monthly}",
"products": [
{
"product": {
"id": "{product id}",
"name": "{product name}"
}
}
],
"form": {
"formItems": [
{
"id": "{form item identifier}",
"statement": "{question statement}",
"imageUrl": "{image associated to question}",
"type": "{type of question: 0 => text, 1 => single-choice, 2 => multi-choice, 3 => image}",
"typeString": "{question type as text: Text, Single-Choice, Multi-Choice, Image}",
"fields": [
{
"id": "{field id}",
"name": "{name of field - not used for Text or Image questions}",
"imageUrl": "{final image url, used for consultation}",
"imageUrlBase64": "{image decoded in base64 - only for Image form items}",
"value": "{value of consumer answer}",
"hasPoints": "{whether the question accumulates points}",
"points": "{points of question}"
}
],
"totalPoints": "{maximum total possible points}",
"minimalPoints": "{minimum points to approve}"
}
]
}
}
Error responses:
| HTTP | errorCode | Cause |
|---|---|---|
| 404 | PROMOTION_NOT_FOUND | promotion id doesn't exist for this tenant. |
| 401 / 406 | INVALID_HEADER_CONTENT | Missing/invalid issy-token or issy-tenant-id. |
| 500 | INTERNAL_ERROR | Unexpected server error. |
Available rewards of a Loyalty Program
Get list of available rewards for a Loyalty Program. The response is a bare JSON array.
GET /rewards/{promotion id}
RESPONSE (200)
[
{
"id": "{reward id}",
"tenantId": "{tenant id}",
"name": "{reward name}",
"tags": "{array of strings - free-form labels assigned to the reward in the backoffice (e.g. ["summer", "vip"]). Trimmed, case-insensitively de-duplicated, returned in the order they were saved. Always an array, [] when there are no tags}",
"description": "{reward description}",
"termsAndConditions": "{HTML string - reward terms and conditions, see note below. Can be null}",
"endPromotionDesc": "{description to show when the redemption process finishes}",
"code": "{reward unique code}",
"enabled": "{reward enabled}",
"mainImageUrl": "{reward image url}",
"type": "{reward type}",
"prizeType": "{reward prize type}",
"requirePOPCode": "{whether redeeming requires a POP code}",
"mechanicType": "{reward mechanic type}",
"startDate": "{date when the reward becomes available}",
"endDate": "{date when the reward stops being available}",
"fraudPreventionModuleActivation": "{is active the fraud prevention module}",
"policyPrivacyUrl": "{reward privacy policy url}",
"apiRequireIssyTermsAcceptance": "{true/false, never null - see Terms & Conditions Acceptance in Get Started. camelCase here (list endpoint)}",
"requireAgegateConfiguration": "{true/false, never null - the reward's own value, always false/0 because the age gate can't be configured on a reward. Redemption is gated by the parent Loyalty Program's requireAgegateConfiguration/minimumAge (GET /loyaltyprograms/{id}) - see Age Gate in Get Started}",
"minimumAge": "{integer, never null - always 0 for a reward, see requireAgegateConfiguration above}",
"creditsToRedeem": "{total credits required to redeem the reward}",
"totalRedemptionLimit": "{max quantity of redemptions}",
"availableRedemptions": "{total redemptions still available for this reward}",
"prizeUrlCallToAction": "{label to show on the redemption code URL}",
"confirmationMessage": "{confirmation message to show when the user wants to redeem the reward}"
}
]
Error responses:
| HTTP | errorCode | Cause |
|---|---|---|
| 404 | PROMOTION_NOT_FOUND | The search returned null (unexpected — normally an empty array is returned instead). |
| 401 / 406 | INVALID_HEADER_CONTENT | Missing/invalid issy-token or issy-tenant-id. |
| 500 | INTERNAL_ERROR | Unexpected server error. |
Get Reward of a Loyalty Program
Get an individual reward's full information, including redemption locations.
GET /rewards/{promotion id}/{reward id}
RESPONSE (200)
{
"id": "{reward id}",
"tenantId": "{tenant id}",
"name": "{reward name}",
"tags": "{array of strings - free-form labels assigned to the reward in the backoffice (e.g. ["summer", "vip"]). Trimmed, case-insensitively de-duplicated, returned in the order they were saved. Always an array, [] when there are no tags}",
"description": "{reward description}",
"termsAndConditions": "{HTML string - reward terms and conditions, see note below. Can be null}",
"endPromotionDesc": "{description to show when the redemption process finishes}",
"code": "{reward unique code}",
"enabled": "{reward enabled}",
"mainImageUrl": "{reward image url}",
"type": "{reward type}",
"prizeType": "{reward prize type}",
"requirePOPCode": "{whether redeeming requires a POP code}",
"requireProofOfPurchase": "{whether redeeming requires an invoice/receipt image - only present in this detail response, not in the list above}",
"mechanicType": "{reward mechanic type}",
"startDate": "{date when the reward becomes available}",
"endDate": "{date when the reward stops being available}",
"fraudPreventionModuleActivation": "{is active the fraud prevention module}",
"policyPrivacyUrl": "{reward privacy policy url}",
"APIRequireIssyTermsAcceptance": "{true/false, never null - see Terms & Conditions Acceptance in Get Started. PascalCase here (detail endpoint, kept for backwards compatibility)}",
"requireAgegateConfiguration": "{true/false, never null - the reward's own value, always false/0 because the age gate can't be configured on a reward. Redemption is gated by the parent Loyalty Program's requireAgegateConfiguration/minimumAge (GET /loyaltyprograms/{id}) - see Age Gate in Get Started}",
"minimumAge": "{integer, never null - always 0 for a reward, see requireAgegateConfiguration above}",
"creditsToRedeem": "{total credits required to redeem the reward}",
"totalRedemptionLimit": "{max quantity of redemptions}",
"availableRedemptions": "{total redemptions still available for this reward}",
"confirmationMessage": "{confirmation message to show when the user wants to redeem the reward}",
"prizeUrlCallToAction": "{label to show on the redemption code URL}",
"locations": [
{
"location": {
"id": "{location id}",
"name": "{location name}",
"address": "{location address}",
"latitude": "{location coordinates latitude}",
"longitude": "{location coordinates longitude}"
}
}
]
}
locationsin this response contains at most the 5 most recently modified locations of the reward, regardless of their status. To get the full list of available locations of a reward (or of a loyalty program), use Get Promotion Locations with the reward id.
Error responses:
| HTTP | errorCode | Cause |
|---|---|---|
| 404 | LOYALTY_PROGRAM_NOT_FOUND | reward id doesn't exist, or doesn't belong to promotion id. |
| 401 / 406 | INVALID_HEADER_CONTENT | Missing/invalid issy-token or issy-tenant-id. |
| 500 | INTERNAL_ERROR | Unexpected server error. |
Redeem reward
Use user credits to redeem a reward.
POST /redeemreward
REQUEST
{
"PromotionId": "{Promotion id of the Loyalty Program the user is participating in}",
"UserId": "{user identifier}",
"SessionId": "{required only if the Loyalty Program is issy-managed - see User Session in Get Started}",
"Trafficsource": "{traffic source or UTM value}",
"RewardId": "{reward identifier}"
}
RESPONSE (200)
{
"redemptionId": "{Promotion location user participation Id created for this redemption}",
"codeType": "{QR or CODE}",
"redemptionCode": "{QR url or alphanumeric code}",
"redemptionUrl": "{URL to show the user, associated to the code}",
"confirmationMessage": "{Per-code banner message configured for this code (Multicode) or reward, when set - null otherwise}"
}
This endpoint doesn't run the Terms & Conditions gate (Get Started) — it's not affected by AcceptedTermsAndConditions/MarketingOptIn/BrandOwnerOptin.
It does run the Age Gate, evaluated against the requireAgegateConfiguration/minimumAge of the Loyalty Program the reward belongs to, using the user's stored birth date. It's checked before any credits are deducted or the redemption is created.
Error responses:
| HTTP | errorCode | Cause |
|---|---|---|
| 401 | INVALID_PARAMS | UserId doesn't exist, or has no participation profile for PromotionId. |
| 401 | LOGIN_REQUIRED | Loyalty Program is issy-managed and no SessionId was sent. |
| 401 | INVALID_SESSION | SessionId is missing/expired, or belongs to a different UserId. |
| 404 | PROMOTION_NOT_FOUND | PromotionId or RewardId doesn't exist. |
| 406 | PROMOTION_INVALID_FOR_ACTION | RewardId doesn't point to a reward (IsReward is false). |
| 406 | BIRTHDATE_REQUIRED | The reward's Loyalty Program has the age gate enabled and the user has no birth date stored. |
| 406 | AGE_REQUIREMENT_NOT_MET | The reward's Loyalty Program has the age gate enabled and the user is younger than its minimumAge. |
| 406 | INSUFFICIENT_CREDITS | Not enough credits available to cover the reward's cost. |
| 451 | REDEMPTIONS_UNAVAILABLE | The reward's total redemption limit has been reached. |
| 406 | INVALID_HEADER_CONTENT | Missing/invalid issy-token or issy-tenant-id. |
| 500 | REDEMPTION_CODE_UNAVAILABLE | No code could be assigned for this reward (e.g. a Multicode pool with no Available codes left). |
| 500 | INTERNAL_ERROR | Unexpected server error. |
error response example
{
"success": false,
"errorCode": "INSUFFICIENT_CREDITS",
"message": "Not credits available"
}
Transfer credits
Executes the transfer of credits between two users, within the same promotion.
POST /transfercredits
REQUEST
{
"PromotionId": "{Promotion id}",
"SenderUserId": "{sender user identifier}",
"ReceiverUserId": "{receiver user identifier}",
"SessionId": "{required only if the Loyalty Program is issy-managed - must belong to SenderUserId, see User Session in Get Started}",
"Credits": "{Credits to transfer}"
}
RESPONSE (200)
{
"transferCreditsId": "{id of the credit transfer}"
}
This endpoint doesn't run the Terms & Conditions gate (Get Started).
Error responses:
| HTTP | errorCode | Cause |
|---|---|---|
| 412 | INVALID_USERS_PARAMS | SenderUserId and ReceiverUserId are the same. |
| 401 | LOGIN_REQUIRED | Loyalty Program is issy-managed and no SessionId was sent. |
| 401 | INVALID_SESSION | SessionId is missing/expired, or doesn't belong to SenderUserId. |
| 404 | USER_PROFILE_NOT_FOUND | Sender or receiver has no participation profile for PromotionId. |
| 404 | PROMOTION_NOT_FOUND | PromotionId doesn't exist for this tenant. |
| 406 | INSUFFICIENT_CREDITS | Sender doesn't have enough credits available. |
| 406 | INVALID_HEADER_CONTENT | Missing/invalid issy-token or issy-tenant-id. |
| 500 | INTERNAL_ERROR | Unexpected server error. |