# Programs

Earning and spending programs management

## Get earning programs

> Retrieve all earning programs for the authenticated shop

```json
{"openapi":"3.0.0","info":{"title":"Joy Loyalty Program - REST API v2","version":"2.0.0"},"tags":[{"name":"Programs","description":"Earning and spending programs management"}],"servers":[{"url":"https://dev-api.joy.so","description":"Staging"},{"url":"https://api.joy.so","description":"Production"}],"security":[{"JoyAuth":[],"JoySecretAuth":[]}],"components":{"securitySchemes":{"JoyAuth":{"type":"apiKey","in":"header","name":"X-Joy-Loyalty-App-Key"}},"schemas":{"ListResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"type":"object"}},"meta":{"type":"object","properties":{"count":{"type":"integer","description":"Number of items returned"}}}}}]},"SuccessResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object"},"meta":{"type":"object","description":"Additional metadata such as counts and pagination"},"message":{"type":"string"},"timestamp":{"type":"string","format":"date-time"}}},"Program":{"type":"object","description":"Loyalty program (earning or spending) with all fields that may be returned by the API. Note: Actual responses may only include non-null fields due to automatic filtering.","properties":{"id":{"type":"string"},"title":{"type":"string"},"type":{"type":"string","enum":["earning","spending","tier_spending","tier"]},"event":{"type":"string"},"status":{"type":"boolean"},"priority":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"expired":{"type":"boolean"},"isDraft":{"type":"boolean"},"earnBy":{"type":"string","enum":["price","order"]},"rateMoney":{"type":"number"},"earnPoint":{"type":"integer"},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"autoRemovePoints":{"type":"boolean"},"appliedPlaceOrderTo":{"type":"string","enum":["all","vip-tier"]},"appliedSource":{"type":"array","items":{"type":"string"}},"translateTitle":{"type":"object"},"typeMilestone":{"type":"string"},"milestones":{"type":"array","items":{"type":"object"}},"spendPoint":{"type":"integer"},"earnAmount":{"type":"number"},"redeemType":{"type":"string"},"appliedTo":{"type":"string"},"appliedCollectionIds":{"type":"array","items":{"type":"string"}},"redeemIn":{"type":"string","enum":["available_in_pos","available_in_online_store"]},"orderReq":{"type":"string","enum":["none","min_amount"]},"orderReqAmount":{"type":"number"},"minSpendPoint":{"type":"string"},"maxSpendPoint":{"type":"string"},"expiredTime":{"type":"string"},"userAvailability":{"type":"string","enum":["allUsers","userRedeemed"]},"showLoyaltyPage":{"type":"boolean"},"limitRedeem":{"type":"string","enum":["redeemWithoutLimit","redeemLimit"]},"totalLimitationRedeem":{"type":"integer"},"combinedWith":{"type":"array","items":{"type":"string"}},"specificProducts":{"type":"array","items":{"type":"object"}},"specificProductIds":{"type":"array","items":{"type":"string"}},"specificCollections":{"type":"array","items":{"type":"object"}},"variantIds":{"type":"array","items":{"type":"string"}},"freeProductIds":{"type":"array","items":{"type":"string"}},"giftStatus":{"type":"string","enum":["none","hot","expiring-soon"]},"excludeProducts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"image":{"type":"object","properties":{"src":{"type":"string"}}}}}},"includeProducts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"image":{"type":"object","properties":{"src":{"type":"string"}}}}}},"conditions":{"type":"array","items":{"type":"object"}},"earnPointsTiers":{"type":"array","items":{"type":"object","properties":{"earnPoint":{"type":"integer"},"rateMoney":{"type":"number"}}}},"roundingMethod":{"type":"string","enum":["round","floor","ceil"]},"skipEarnPointGuest":{"type":"boolean"}}},"ErrorResponse":{"type":"object","properties":{"success":{"type":"boolean"},"error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string"},"statusCode":{"type":"integer"},"details":{"type":"object"}}},"timestamp":{"type":"string","format":"date-time"}}}}},"paths":{"/rest_api/v2/programs/earning":{"get":{"tags":["Programs"],"summary":"Get earning programs","description":"Retrieve all earning programs for the authenticated shop","responses":{"200":{"description":"List of earning programs","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ListResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Program"}}}}]}}}},"403":{"description":"Plan upgrade required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Create earning program

> Create a new earning program. The \`event\` field determines the program type and which fields are required/validated.\
> \
> \*\*Common required fields (all events):\*\* \`title\`, \`event\`\
> \*\*Common optional fields:\*\* \`status\` (default varies by event), \`priority\`, \`isDraft\`, \`description\`, \`startDate\`, \`endDate\`\
> \
> \---\
> \
> \### Event Groups & Required Fields\
> \
> \*\*Simple one-time rewards:\*\*\
> \
> \
> \| Event | Required | Optional |\
> \|-------|----------|----------|\
> \| \`sign\_up\` | \`earnPoint\` | \`typeEarn\` (point / percentage / amount). Percentage: earnPoint <= 100. Discount fields (prefix, appliedTo, expiredAfter, combinedWith) only when typeEarn != "point" |\
> \| \`sign\_up\_newsletter\` | \`earnPoint\` | \`autoSyncExistingNewsletter\`, \`earnWidgetPopup\`, \`excludedSubscribedBefore\`. Points only (typeEarn forced to "point") |\
> \| \`birthday\` | \`earnPoint\` (unless multi-reward) | \`typeEarn\` (point / percentage / amount / bxgy / free\_gift), \`dateFormat\` (MM/DD, DD/MM, MM/DD/YYYY, DD/MM/YYYY, YYYY/MM/DD), \`typeDeliveryMethod\` (automatic / manual), \`typeRewardTiming\` (exact\_birthday / begin\_of\_month / around\_birthday). around\_birthday requires manual delivery |\
> \| \`member\_anniversary\` | \`earnPoint\`, \`duration\` | \`duration\` pattern: "N unit" e.g. "1 Y", "6 M", "30 D". Units: Y, M, D, W, min |\
> \
> \
> \*\*Social engagement\*\* (all require \`earnPoint\`):\
> \
> \
> \| Event | Required | Optional |\
> \|-------|----------|----------|\
> \| \`like\_facebook\`, \`follow\_instagram\`, \`follow\_twitter\`, \`follow\_pinterest\`, \`follow\_tiktok\`, \`subscribe\_youtube\`, \`join\_discord\`, \`join\_telegram\` | \`urlAccount\` | — |\
> \| \`share\_facebook\`, \`share\_twitter\` | \`urlAccount\` | \`enabledAntiCheat\`, \`message\` |\
> \| \`join\_whatsapp\`, \`join\_line\` | \`typeJoin\` (link / qr\_code) | When link: \`urlAccount\`. When qr\_code + whatsapp: \`imageQRCode\` |\
> \| \`comment\_instagram\` | — | \`postType\` (all\_posts / specific\_posts), keyword filters, auto-reply, DM |\
> \| \`story\_mention\_instagram\` | — | DM notification support |\
> \
> Do NOT send \`social\` or \`action\` fields — they are auto-derived from event.\
> \
> \
> \*\*Purchase-based:\*\*\
> \
> \
> \| Event | Required | Optional |\
> \|-------|----------|----------|\
> \| \`place\_order\`, \`place\_order\_subscription\` | \`earnPoint\` | \`earnBy\` (price / fixed\_order / quantity / order). price: requires \`rateMoney\`. quantity: requires \`rateItem\`. \`roundingMethod\` (roundingNearest / roundingUp / roundingDown), \`autoRemovePoints\`, \`skipEarnPointGuest\`, rule engine: \`statusUseCondition\`, \`conditions\`, \`typeCondition\` |\
> \
> typeEarn limited to "point" or "store\_credit" for purchase events.\
> \
> \
> \*\*Content & action\*\* (all require \`earnPoint\` unless noted):\
> \
> \
> \| Event | Required | Optional |\
> \|-------|----------|----------|\
> \| \`write\_review\` | \`reviewApp\` (airReviewApp, judgeMeApp, yotpoApp, feraApp, etc.) | Shopify Flow apps also require \`descriptionReview\` |\
> \| \`fill\_survey\` | — | \`link\` (survey URL, no https\:// prefix). earnPoint informational only |\
> \| \`submit\_form\` | — | \`autoApprove\`, \`instantRewardEnabled\`, \`instantRewardPoints\`, \`requireImage\` |\
> \| \`submit\_receipt\` | — | Admin approval required. Supports point/percentage/amount |\
> \| \`google\_maps\_review\` | \`urlLocation\` | \`isInstantPoint\`, \`instantPoint\` (must be < earnPoint) |\
> \| \`interact\_website\` | — | Uses \`limitUnit\` / \`limitInterval\` always. Earns once per period |\
> \| \`streak\_interact\_website\` | \`description\`, \`startDate\`, \`endDate\`, \`streakMilestones\` | streakMilestones: array with day (3/5/7) and earnPoint |\
> \| \`custom\_program\` | \`typeCustom\` (visit\_page\_{id} / completed\_profile\_{id} / shopify\_flow\_trigger\_{id} / custom\_trigger\_{id}) | visit\_page: \`linkVisit\`. completed\_profile: \`fieldProfile\` (name/email/phone) |\
> \| \`milestone\` | \`typeMilestone\`, \`milestones\` (array, min 1) | typeMilestone values: milestone\_order, milestone\_amount\_spent, milestone\_earned\_point, milestone\_reviews, milestone\_inactivity, milestone\_referral, milestone\_subscription |\
> \
> \---\
> \
> \### Date Range (shared)\
> \- \`startDate\` — optional. If omitted, defaults to current time (program active immediately). \*\*Required for streak\_interact\_website.\*\*\
> \- \`endDate\` — optional. If omitted, program never expires. \*\*Required for streak\_interact\_website.\*\*\
> \- Note: spending programs also default startDate to current time if omitted. They use \`expiredAfter\`/\`expiredTime\` for coupon expiration after redemption.\
> \
> \### Earning Frequency (shared across most events)\
> \- \`hasLimit\` (default varies by event) — enable earning frequency cap\
> \- \`limitUnit\` (minute|hour|day|week|month|year|lifetime, default: "lifetime")\
> \- \`limitInterval\` — required when hasLimit=true AND limitUnit != "lifetime"\
> \
> \### VIP Tier Overrides (shared)\
> \- \`isAppliedVipTier\` (default: false) — enable tier-specific earn rates\
> \- \`earnPointsTiers\` (object) — tier ID → earnPoint mapping<br>

```json
{"openapi":"3.0.0","info":{"title":"Joy Loyalty Program - REST API v2","version":"2.0.0"},"tags":[{"name":"Programs","description":"Earning and spending programs management"}],"servers":[{"url":"https://dev-api.joy.so","description":"Staging"},{"url":"https://api.joy.so","description":"Production"}],"security":[{"JoyAuth":[],"JoySecretAuth":[]}],"components":{"securitySchemes":{"JoyAuth":{"type":"apiKey","in":"header","name":"X-Joy-Loyalty-App-Key"}},"schemas":{"SuccessResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object"},"meta":{"type":"object","description":"Additional metadata such as counts and pagination"},"message":{"type":"string"},"timestamp":{"type":"string","format":"date-time"}}},"Program":{"type":"object","description":"Loyalty program (earning or spending) with all fields that may be returned by the API. Note: Actual responses may only include non-null fields due to automatic filtering.","properties":{"id":{"type":"string"},"title":{"type":"string"},"type":{"type":"string","enum":["earning","spending","tier_spending","tier"]},"event":{"type":"string"},"status":{"type":"boolean"},"priority":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"expired":{"type":"boolean"},"isDraft":{"type":"boolean"},"earnBy":{"type":"string","enum":["price","order"]},"rateMoney":{"type":"number"},"earnPoint":{"type":"integer"},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"autoRemovePoints":{"type":"boolean"},"appliedPlaceOrderTo":{"type":"string","enum":["all","vip-tier"]},"appliedSource":{"type":"array","items":{"type":"string"}},"translateTitle":{"type":"object"},"typeMilestone":{"type":"string"},"milestones":{"type":"array","items":{"type":"object"}},"spendPoint":{"type":"integer"},"earnAmount":{"type":"number"},"redeemType":{"type":"string"},"appliedTo":{"type":"string"},"appliedCollectionIds":{"type":"array","items":{"type":"string"}},"redeemIn":{"type":"string","enum":["available_in_pos","available_in_online_store"]},"orderReq":{"type":"string","enum":["none","min_amount"]},"orderReqAmount":{"type":"number"},"minSpendPoint":{"type":"string"},"maxSpendPoint":{"type":"string"},"expiredTime":{"type":"string"},"userAvailability":{"type":"string","enum":["allUsers","userRedeemed"]},"showLoyaltyPage":{"type":"boolean"},"limitRedeem":{"type":"string","enum":["redeemWithoutLimit","redeemLimit"]},"totalLimitationRedeem":{"type":"integer"},"combinedWith":{"type":"array","items":{"type":"string"}},"specificProducts":{"type":"array","items":{"type":"object"}},"specificProductIds":{"type":"array","items":{"type":"string"}},"specificCollections":{"type":"array","items":{"type":"object"}},"variantIds":{"type":"array","items":{"type":"string"}},"freeProductIds":{"type":"array","items":{"type":"string"}},"giftStatus":{"type":"string","enum":["none","hot","expiring-soon"]},"excludeProducts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"image":{"type":"object","properties":{"src":{"type":"string"}}}}}},"includeProducts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"image":{"type":"object","properties":{"src":{"type":"string"}}}}}},"conditions":{"type":"array","items":{"type":"object"}},"earnPointsTiers":{"type":"array","items":{"type":"object","properties":{"earnPoint":{"type":"integer"},"rateMoney":{"type":"number"}}}},"roundingMethod":{"type":"string","enum":["round","floor","ceil"]},"skipEarnPointGuest":{"type":"boolean"}}},"ErrorResponse":{"type":"object","properties":{"success":{"type":"boolean"},"error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string"},"statusCode":{"type":"integer"},"details":{"type":"object"}}},"timestamp":{"type":"string","format":"date-time"}}}}},"paths":{"/rest_api/v2/programs/earning":{"post":{"tags":["Programs"],"summary":"Create earning program","description":"Create a new earning program. The `event` field determines the program type and which fields are required/validated.\n\n**Common required fields (all events):** `title`, `event`\n**Common optional fields:** `status` (default varies by event), `priority`, `isDraft`, `description`, `startDate`, `endDate`\n\n---\n\n### Event Groups & Required Fields\n\n**Simple one-time rewards:**\n\n\n| Event | Required | Optional |\n|-------|----------|----------|\n| `sign_up` | `earnPoint` | `typeEarn` (point / percentage / amount). Percentage: earnPoint <= 100. Discount fields (prefix, appliedTo, expiredAfter, combinedWith) only when typeEarn != \"point\" |\n| `sign_up_newsletter` | `earnPoint` | `autoSyncExistingNewsletter`, `earnWidgetPopup`, `excludedSubscribedBefore`. Points only (typeEarn forced to \"point\") |\n| `birthday` | `earnPoint` (unless multi-reward) | `typeEarn` (point / percentage / amount / bxgy / free_gift), `dateFormat` (MM/DD, DD/MM, MM/DD/YYYY, DD/MM/YYYY, YYYY/MM/DD), `typeDeliveryMethod` (automatic / manual), `typeRewardTiming` (exact_birthday / begin_of_month / around_birthday). around_birthday requires manual delivery |\n| `member_anniversary` | `earnPoint`, `duration` | `duration` pattern: \"N unit\" e.g. \"1 Y\", \"6 M\", \"30 D\". Units: Y, M, D, W, min |\n\n\n**Social engagement** (all require `earnPoint`):\n\n\n| Event | Required | Optional |\n|-------|----------|----------|\n| `like_facebook`, `follow_instagram`, `follow_twitter`, `follow_pinterest`, `follow_tiktok`, `subscribe_youtube`, `join_discord`, `join_telegram` | `urlAccount` | — |\n| `share_facebook`, `share_twitter` | `urlAccount` | `enabledAntiCheat`, `message` |\n| `join_whatsapp`, `join_line` | `typeJoin` (link / qr_code) | When link: `urlAccount`. When qr_code + whatsapp: `imageQRCode` |\n| `comment_instagram` | — | `postType` (all_posts / specific_posts), keyword filters, auto-reply, DM |\n| `story_mention_instagram` | — | DM notification support |\n\nDo NOT send `social` or `action` fields — they are auto-derived from event.\n\n\n**Purchase-based:**\n\n\n| Event | Required | Optional |\n|-------|----------|----------|\n| `place_order`, `place_order_subscription` | `earnPoint` | `earnBy` (price / fixed_order / quantity / order). price: requires `rateMoney`. quantity: requires `rateItem`. `roundingMethod` (roundingNearest / roundingUp / roundingDown), `autoRemovePoints`, `skipEarnPointGuest`, rule engine: `statusUseCondition`, `conditions`, `typeCondition` |\n\ntypeEarn limited to \"point\" or \"store_credit\" for purchase events.\n\n\n**Content & action** (all require `earnPoint` unless noted):\n\n\n| Event | Required | Optional |\n|-------|----------|----------|\n| `write_review` | `reviewApp` (airReviewApp, judgeMeApp, yotpoApp, feraApp, etc.) | Shopify Flow apps also require `descriptionReview` |\n| `fill_survey` | — | `link` (survey URL, no https:// prefix). earnPoint informational only |\n| `submit_form` | — | `autoApprove`, `instantRewardEnabled`, `instantRewardPoints`, `requireImage` |\n| `submit_receipt` | — | Admin approval required. Supports point/percentage/amount |\n| `google_maps_review` | `urlLocation` | `isInstantPoint`, `instantPoint` (must be < earnPoint) |\n| `interact_website` | — | Uses `limitUnit` / `limitInterval` always. Earns once per period |\n| `streak_interact_website` | `description`, `startDate`, `endDate`, `streakMilestones` | streakMilestones: array with day (3/5/7) and earnPoint |\n| `custom_program` | `typeCustom` (visit_page_{id} / completed_profile_{id} / shopify_flow_trigger_{id} / custom_trigger_{id}) | visit_page: `linkVisit`. completed_profile: `fieldProfile` (name/email/phone) |\n| `milestone` | `typeMilestone`, `milestones` (array, min 1) | typeMilestone values: milestone_order, milestone_amount_spent, milestone_earned_point, milestone_reviews, milestone_inactivity, milestone_referral, milestone_subscription |\n\n---\n\n### Date Range (shared)\n- `startDate` — optional. If omitted, defaults to current time (program active immediately). **Required for streak_interact_website.**\n- `endDate` — optional. If omitted, program never expires. **Required for streak_interact_website.**\n- Note: spending programs also default startDate to current time if omitted. They use `expiredAfter`/`expiredTime` for coupon expiration after redemption.\n\n### Earning Frequency (shared across most events)\n- `hasLimit` (default varies by event) — enable earning frequency cap\n- `limitUnit` (minute|hour|day|week|month|year|lifetime, default: \"lifetime\")\n- `limitInterval` — required when hasLimit=true AND limitUnit != \"lifetime\"\n\n### VIP Tier Overrides (shared)\n- `isAppliedVipTier` (default: false) — enable tier-specific earn rates\n- `earnPointsTiers` (object) — tier ID → earnPoint mapping\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title","event"],"properties":{"title":{"type":"string","description":"Program display title"},"event":{"type":"string","enum":["sign_up","sign_up_newsletter","like_facebook","follow_instagram","follow_twitter","follow_pinterest","follow_tiktok","subscribe_youtube","join_discord","join_telegram","share_facebook","share_twitter","join_whatsapp","join_line","comment_instagram","story_mention_instagram","google_maps_review","write_review","fill_survey","submit_form","member_anniversary","custom_program","submit_receipt","interact_website","streak_interact_website","birthday","place_order","place_order_subscription","milestone"],"description":"Event type — determines required fields. See endpoint description for details per event."},"status":{"type":"boolean","default":true,"description":"Whether the program is active. Default varies by event (true for sign_up/place_order, false for social/content events)."},"earnPoint":{"type":"number","description":"Points to award. Required for most events. For percentage typeEarn, must be <= 100."},"typeEarn":{"type":"string","enum":["point","percentage","amount"],"default":"point","description":"Reward type. 'point' awards loyalty points. 'percentage'/'amount' create discount coupons. sign_up, member_anniversary, submit_receipt support point/percentage/amount. birthday supports all five types: point, percentage, amount, bxgy, free_gift. place_order only supports point/store_credit."},"earnBy":{"type":"string","enum":["price","fixed_order","quantity","order"],"default":"price","description":"How points are calculated for place_order events. price: spend $rateMoney earn $earnPoint points. fixed_order: flat points per order (requires rateMoney). quantity: points per item (requires rateItem). order: fixed earnPoint per order."},"rateMoney":{"type":"number","description":"Currency-to-points ratio. Required when earnBy is 'price' or 'fixed_order'."},"rateItem":{"type":"number","description":"Item-to-points ratio. Required when earnBy is 'quantity'."},"urlAccount":{"type":"string","description":"Social media profile/page URL. Required for simple social, share social events. Must start with http:// or https://."},"urlLocation":{"type":"string","description":"Google Maps location URL. Required for google_maps_review."},"typeJoin":{"type":"string","enum":["link","qr_code"],"default":"link","description":"Join method for join_whatsapp, join_line. 'link': urlAccount required. 'qr_code': imageQRCode required (whatsapp only)."},"imageQRCode":{"type":"string","description":"QR code image URL. Required when typeJoin='qr_code' and event='join_whatsapp'."},"reviewApp":{"type":"string","enum":["airReviewApp","judgeMeApp","yotpoApp","feraApp","reviewRocketApp","klaviyoApp","stampedApp","looxApp","okendoApp","ReviewsIoApp","growaveApp","typdalApp"],"description":"Review app integration. Required for write_review."},"duration":{"type":"string","description":"Anniversary interval pattern 'N unit' (e.g. '1 Y', '6 M', '30 D', '2 W'). Valid units: Y, M, D, W, min. Required for member_anniversary."},"typeCustom":{"type":"string","description":"Custom program type '{prefix}_{id}'. Prefixes: visit_page, completed_profile, shopify_flow_trigger, custom_trigger. Required for custom_program."},"typeMilestone":{"type":"string","enum":["milestone_order","milestone_amount_spent","milestone_earned_point","milestone_reviews","milestone_inactivity","milestone_referral","milestone_subscription"],"description":"Milestone trigger type. Required for milestone event."},"milestones":{"type":"array","description":"Milestone thresholds array (min 1). Required for milestone event. Each item: {value, unit? (for inactivity), rewards?, rewardLogic?}.","items":{"type":"object","properties":{"value":{"type":"integer","description":"Threshold value"},"unit":{"type":"string","enum":["day","month","year"],"description":"Time unit for milestone_inactivity only"},"rewardLogic":{"type":"string","enum":["grantAll","customerPicks"],"default":"grantAll"},"rewards":{"type":"array","items":{"type":"object"}}}}},"streakMilestones":{"type":"array","description":"Streak bonus milestones. Required for streak_interact_website. Each: {day: 3|5|7, earnPoint: number}.","items":{"type":"object","properties":{"day":{"type":"integer","enum":[3,5,7]},"earnPoint":{"type":"integer"}}}},"roundingMethod":{"type":"string","enum":["roundingNearest","roundingUp","roundingDown"],"default":"roundingDown","description":"Rounding for place_order point calculations."},"autoRemovePoints":{"type":"boolean","default":true,"description":"Remove earned points on order refund. For place_order."},"skipEarnPointGuest":{"type":"boolean","default":true,"description":"Skip earning for guest customers. For place_order."},"appliedPlaceOrderTo":{"type":"string","enum":["all","vip-tier","member_and_guest"],"default":"all","description":"Who can earn from place_order."},"hasLimit":{"type":"boolean","default":true,"description":"Enable earning frequency cap."},"limitUnit":{"type":"string","enum":["minute","hour","day","week","month","year","lifetime"],"default":"lifetime","description":"Full enum shown. Some events restrict to subset: social events allow day-lifetime only, birthday allows year/lifetime only, place_order allows minute-lifetime."},"limitInterval":{"type":"integer","description":"Number of limitUnit periods. Required when hasLimit=true AND limitUnit != 'lifetime'."},"isAppliedVipTier":{"type":"boolean","default":false,"description":"Enable tier-specific earn rates. When true, use earnPointsTiers to override earnPoint per tier."},"earnPointsTiers":{"type":"object","description":"Tier-specific earn rates. Keys are tier IDs, values are earnPoint overrides."},"priority":{"type":"integer","description":"Program priority order. Lower numbers = higher priority."},"isDraft":{"type":"boolean","description":"Save as draft without activating."},"startDate":{"type":"string","format":"date-time","description":"Program activation date (ISO 8601). If omitted, defaults to current time (program active immediately). Required for streak_interact_website."},"endDate":{"type":"string","format":"date-time","description":"Program expiration date (ISO 8601). If omitted, program never expires. Required for streak_interact_website."},"description":{"type":"string","description":"Program description shown to customers. Used by social, submit_form, streak_interact_website, and other events."},"enabledAntiCheat":{"type":"boolean","default":true,"description":"Anti-cheat verification. Used by share social, comment_instagram, story_mention_instagram, write_review, place_order, custom_program."},"prefix":{"type":"string","default":"JOY-","description":"Discount code prefix. For discount typeEarn (percentage/amount)."},"appliedTo":{"type":"string","enum":["all","specific","sf_product"],"default":"all","description":"Discount product scope. For discount typeEarn."},"expiredAfter":{"type":"string","enum":["permanent","7D","14D","1M","3M","6M","1Y","2Y","specific"],"default":"permanent","description":"Discount coupon expiration. For discount typeEarn."},"combinedWith":{"type":"array","items":{"type":"string","enum":["orderDiscounts","productDiscounts","shippingDiscounts"]},"description":"Discount combination rules. For discount typeEarn."},"appliedDiscountToSaleChannel":{"type":"string","enum":["applyOnlineStore","applyPos","applyAll"],"default":"applyOnlineStore","description":"Sale channel for discount coupons. For discount typeEarn (percentage/amount)."},"userAvailability":{"type":"string","default":"userRedeemed","description":"Who can use the coupon. For discount typeEarn."},"appliedCollectionIds":{"type":"array","items":{"type":"string"},"description":"Collection IDs. Required when appliedTo='specific'. For discount typeEarn."},"specificProducts":{"type":"array","items":{"type":"object"},"description":"Product objects (max 100). Required when appliedTo='sf_product'. For discount typeEarn."},"specificProductIds":{"type":"array","items":{"type":"string"},"description":"Product IDs (max 100). For discount typeEarn."},"orderReq":{"type":"string","enum":["none","min_amount","min_quantity"],"default":"none","description":"Order requirement for discount. For discount typeEarn."},"orderReqAmount":{"type":"number","description":"Min order amount. Required when orderReq='min_amount'."},"orderReqQuantity":{"type":"number","description":"Min order quantity. Required when orderReq='min_quantity'."},"expiredTime":{"type":"string","description":"Specific expiration datetime. Required when expiredAfter='specific'."},"isUsePrefixDiscountCode":{"type":"boolean","default":true,"description":"Whether to use prefix for discount codes. For discount typeEarn."},"dateFormat":{"type":"string","enum":["MM/DD","DD/MM","MM/DD/YYYY","DD/MM/YYYY","YYYY/MM/DD"],"default":"MM/DD","description":"Birthday date format. For birthday event."},"typeDeliveryMethod":{"type":"string","enum":["automatic","manual"],"default":"automatic","description":"Reward delivery method. For birthday event. 'around_birthday' timing requires 'manual'."},"typeRewardTiming":{"type":"string","enum":["exact_birthday","begin_of_month","around_birthday"],"default":"exact_birthday","description":"When to deliver birthday reward. 'around_birthday' only available with typeDeliveryMethod='manual'."},"isUseLayoutMultiRewards":{"type":"boolean","default":false,"description":"Enable multi-reward layout (customer picks between point/discount/free_gift). For birthday."},"message":{"type":"string","description":"Share message text. For share_facebook, share_twitter."},"postType":{"type":"string","enum":["all_posts","specific_posts"],"default":"all_posts","description":"Target posts. For comment_instagram. When 'specific_posts': specificPostIds required."},"specificPostIds":{"type":"array","items":{"type":"string"},"description":"Instagram post IDs. Required when postType='specific_posts'."},"enableIncludeKeywords":{"type":"boolean","default":false,"description":"Enable keyword inclusion filter. For comment_instagram."},"includeKeywords":{"type":"array","items":{"type":"string"},"description":"Required keywords. Required when enableIncludeKeywords=true."},"enableExcludeKeywords":{"type":"boolean","default":false,"description":"Enable keyword exclusion filter. For comment_instagram."},"excludeKeywords":{"type":"array","items":{"type":"string"},"description":"Blocked keywords. Required when enableExcludeKeywords=true."},"enableAutoReply":{"type":"boolean","default":false,"description":"Auto-reply to Instagram comments. For comment_instagram."},"autoReplyMessage":{"type":"string","description":"Auto-reply message. Supports {points} placeholder. Required when enableAutoReply=true."},"enableDMNotification":{"type":"boolean","default":false,"description":"Send DM notification. For comment_instagram, story_mention_instagram."},"dmMessage":{"type":"string","description":"DM message text. Supports {points}, {store_url}. Required when enableDMNotification=true."},"descriptionReview":{"type":"string","description":"Review description. Required for Shopify Flow review apps: stampedApp, looxApp, okendoApp, ReviewsIoApp, growaveApp, typdalApp."},"isAppliedExtraPoints":{"type":"boolean","default":true,"description":"Award bonus points for media attachments. For write_review."},"extraPoints":{"type":"number","description":"Bonus points for media. Required when isAppliedExtraPoints=true (except reviewRocketApp)."},"autoApprove":{"type":"boolean","default":false,"description":"Auto-approve form submissions. For submit_form. If false, admin reviews before points awarded."},"instantRewardEnabled":{"type":"boolean","default":false,"description":"Award partial points immediately when autoApprove=false. For submit_form."},"instantRewardPoints":{"type":"integer","description":"Partial points to award immediately. Required when autoApprove=false AND instantRewardEnabled=true."},"requireImage":{"type":"boolean","default":false,"description":"Require image upload. For submit_form."},"isInstantPoint":{"type":"boolean","default":false,"description":"Award partial points before admin approval. For google_maps_review."},"instantPoint":{"type":"number","description":"Partial points (must be < earnPoint). Required when isInstantPoint=true."},"linkVisit":{"type":"string","description":"Page URL to visit. Required when typeCustom starts with 'visit_page'."},"fieldProfile":{"type":"array","items":{"type":"string","enum":["name","email","phone"]},"description":"Profile fields to complete. Required when typeCustom starts with 'completed_profile'."},"autoSyncExistingNewsletter":{"type":"boolean","default":false,"description":"Retroactively award points to existing subscribers. For sign_up_newsletter."},"statusUseCondition":{"type":"boolean","default":false,"description":"Enable rule engine conditions. For place_order."},"conditions":{"type":"array","items":{"type":"object"},"description":"Rule engine conditions array. For place_order when statusUseCondition=true."},"excludeProducts":{"type":"array","items":{"type":"object"},"description":"Products to exclude from earning. For place_order."},"includeProducts":{"type":"array","items":{"type":"object"},"description":"Products to include for earning. For place_order."},"earnPointShippingFee":{"type":"boolean","default":false,"description":"Include shipping fee in points calculation. For place_order."},"earnPointTax":{"type":"boolean","default":false,"description":"Include tax in points calculation. For place_order."},"excludeGiftCard":{"type":"boolean","default":false,"description":"Exclude gift card products. For place_order."},"enabledMaxEarnPoint":{"type":"boolean","default":false,"description":"Cap maximum points per order. For place_order."},"maxEarnPoint":{"type":"number","description":"Max points per order. Required when enabledMaxEarnPoint=true."},"excludeFreeGift":{"type":"boolean","default":false,"description":"Exclude free gift items from earning. For place_order."},"excludeStoreCredit":{"type":"boolean","default":false,"description":"Exclude store credit items from earning. For place_order."},"typeProductMatch":{"type":"string","enum":["all","any"],"default":"all","description":"Product condition matching mode. 'all' = all conditions must match, 'any' = any condition matches. For place_order with rule engine."},"typeCondition":{"type":"string","enum":["all","select","group"],"default":"all","description":"Condition type for rule engine. For place_order."},"typeDateRange":{"type":"string","enum":["static_date","dynamic_date"],"description":"Date range type. 'static_date': uses startDate/endDate. 'dynamic_date': uses valueDateRange. For place_order."},"valueDateRange":{"type":"string","enum":["month_of_birth","weekend","specific_day_month","specific_day_week"],"description":"Dynamic date range filter. For place_order with typeDateRange='dynamic_date'."},"priorityPlaceOrder":{"type":"string","default":"99","description":"Rule execution priority (lower = higher). For place_order V2 rule engine."},"stopProcessing":{"type":"boolean","default":false,"description":"Stop evaluating lower-priority rules after this rule matches. For place_order V2."},"excludeOrderTags":{"type":"array","items":{"type":"string"},"description":"Order tags to exclude from earning. For place_order."},"earnWidgetPopup":{"type":"boolean","default":false,"description":"Enable widget popup for newsletter sign-up. For sign_up_newsletter."},"excludedSubscribedBefore":{"type":"boolean","default":false,"description":"Exclude customers who subscribed before program activation. For sign_up_newsletter."},"link":{"type":"string","description":"Survey URL (without https:// prefix). For fill_survey."},"extraPointsForImages":{"type":"number","description":"Bonus points for image attachments. For write_review."},"extraPointsForVideos":{"type":"number","description":"Bonus points for video attachments. For write_review."},"extraPointsMode":{"type":"string","enum":["per_attachment","fixed"],"description":"How bonus points are calculated. 'per_attachment': multiply by count. 'fixed': flat bonus. For write_review."},"enabledMinCharacters":{"type":"boolean","default":false,"description":"Require minimum review length. For write_review."},"minCharacters":{"type":"integer","description":"Minimum character count. Required when enabledMinCharacters=true."},"enableMinStarRating":{"type":"boolean","default":false,"description":"Require minimum star rating. For write_review."},"minStarRating":{"type":"integer","minimum":1,"maximum":5,"description":"Minimum star rating (1-5). Required when enableMinStarRating=true."}}}}}},"responses":{"201":{"description":"Program created successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Program"}}}]}}}},"400":{"description":"Validation error — missing required fields, invalid values, or constraint violations","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Plan upgrade required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Duplicate program. Single-instance events allow only one program per shop.\n**Single-instance events** (1 per shop):\nsign_up, sign_up_newsletter, birthday,\nlike_facebook, share_facebook, follow_instagram, follow_twitter, share_twitter,\nfollow_pinterest, follow_tiktok, subscribe_youtube,\njoin_discord, join_telegram, join_whatsapp,\ninteract_website, streak_interact_website\n\n**Multi-instance events** (no limit):\nplace_order, milestone, custom_program, submit_form,\nwrite_review, fill_survey, submit_receipt, member_anniversary,\ngoogle_maps_review, comment_instagram, story_mention_instagram, join_line\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Get programs by customer eligibility

> Retrieve earning programs for a specific customer. Single earn event programs \
> (like social media follows, sign up, birthday) will include an \`isEarned\` field \
> indicating whether the customer has already earned points from that program.\
> \
> \*\*Customer Identifier Required\*\*: Must provide either \`customerId\` or \`shopifyCustomerId\` parameter.<br>

```json
{"openapi":"3.0.0","info":{"title":"Joy Loyalty Program - REST API v2","version":"2.0.0"},"tags":[{"name":"Programs","description":"Earning and spending programs management"}],"servers":[{"url":"https://dev-api.joy.so","description":"Staging"},{"url":"https://api.joy.so","description":"Production"}],"security":[{"JoyAuth":[],"JoySecretAuth":[]}],"components":{"securitySchemes":{"JoyAuth":{"type":"apiKey","in":"header","name":"X-Joy-Loyalty-App-Key"}},"schemas":{"ListResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"type":"object"}},"meta":{"type":"object","properties":{"count":{"type":"integer","description":"Number of items returned"}}}}}]},"SuccessResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object"},"meta":{"type":"object","description":"Additional metadata such as counts and pagination"},"message":{"type":"string"},"timestamp":{"type":"string","format":"date-time"}}},"Program":{"type":"object","description":"Loyalty program (earning or spending) with all fields that may be returned by the API. Note: Actual responses may only include non-null fields due to automatic filtering.","properties":{"id":{"type":"string"},"title":{"type":"string"},"type":{"type":"string","enum":["earning","spending","tier_spending","tier"]},"event":{"type":"string"},"status":{"type":"boolean"},"priority":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"expired":{"type":"boolean"},"isDraft":{"type":"boolean"},"earnBy":{"type":"string","enum":["price","order"]},"rateMoney":{"type":"number"},"earnPoint":{"type":"integer"},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"autoRemovePoints":{"type":"boolean"},"appliedPlaceOrderTo":{"type":"string","enum":["all","vip-tier"]},"appliedSource":{"type":"array","items":{"type":"string"}},"translateTitle":{"type":"object"},"typeMilestone":{"type":"string"},"milestones":{"type":"array","items":{"type":"object"}},"spendPoint":{"type":"integer"},"earnAmount":{"type":"number"},"redeemType":{"type":"string"},"appliedTo":{"type":"string"},"appliedCollectionIds":{"type":"array","items":{"type":"string"}},"redeemIn":{"type":"string","enum":["available_in_pos","available_in_online_store"]},"orderReq":{"type":"string","enum":["none","min_amount"]},"orderReqAmount":{"type":"number"},"minSpendPoint":{"type":"string"},"maxSpendPoint":{"type":"string"},"expiredTime":{"type":"string"},"userAvailability":{"type":"string","enum":["allUsers","userRedeemed"]},"showLoyaltyPage":{"type":"boolean"},"limitRedeem":{"type":"string","enum":["redeemWithoutLimit","redeemLimit"]},"totalLimitationRedeem":{"type":"integer"},"combinedWith":{"type":"array","items":{"type":"string"}},"specificProducts":{"type":"array","items":{"type":"object"}},"specificProductIds":{"type":"array","items":{"type":"string"}},"specificCollections":{"type":"array","items":{"type":"object"}},"variantIds":{"type":"array","items":{"type":"string"}},"freeProductIds":{"type":"array","items":{"type":"string"}},"giftStatus":{"type":"string","enum":["none","hot","expiring-soon"]},"excludeProducts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"image":{"type":"object","properties":{"src":{"type":"string"}}}}}},"includeProducts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"image":{"type":"object","properties":{"src":{"type":"string"}}}}}},"conditions":{"type":"array","items":{"type":"object"}},"earnPointsTiers":{"type":"array","items":{"type":"object","properties":{"earnPoint":{"type":"integer"},"rateMoney":{"type":"number"}}}},"roundingMethod":{"type":"string","enum":["round","floor","ceil"]},"skipEarnPointGuest":{"type":"boolean"}}},"ErrorResponse":{"type":"object","properties":{"success":{"type":"boolean"},"error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string"},"statusCode":{"type":"integer"},"details":{"type":"object"}}},"timestamp":{"type":"string","format":"date-time"}}}}},"paths":{"/rest_api/v2/programs/earning/eligibility":{"get":{"tags":["Programs"],"summary":"Get programs by customer eligibility","description":"Retrieve earning programs for a specific customer. Single earn event programs \n(like social media follows, sign up, birthday) will include an `isEarned` field \nindicating whether the customer has already earned points from that program.\n\n**Customer Identifier Required**: Must provide either `customerId` or `shopifyCustomerId` parameter.\n","parameters":[{"name":"shopifyCustomerId","in":"query","schema":{"type":"string"},"description":"Shopify customer ID"},{"name":"customerId","in":"query","schema":{"type":"string"},"description":"Internal customer ID"}],"responses":{"200":{"description":"Programs for customer with earned status","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ListResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/Program"},{"type":"object","properties":{"isEarned":{"type":"boolean","description":"Only present for single earn event programs (social media follows, \nsign up, birthday, etc.). Indicates if customer has earned from this program.\n"}}}]}}}}]}}}},"404":{"description":"Customer not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Calculate earning points

> Calculate points that would be earned for given products

```json
{"openapi":"3.0.0","info":{"title":"Joy Loyalty Program - REST API v2","version":"2.0.0"},"tags":[{"name":"Programs","description":"Earning and spending programs management"}],"servers":[{"url":"https://dev-api.joy.so","description":"Staging"},{"url":"https://api.joy.so","description":"Production"}],"security":[{"JoyAuth":[],"JoySecretAuth":[]}],"components":{"securitySchemes":{"JoyAuth":{"type":"apiKey","in":"header","name":"X-Joy-Loyalty-App-Key"}},"schemas":{"SuccessResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object"},"meta":{"type":"object","description":"Additional metadata such as counts and pagination"},"message":{"type":"string"},"timestamp":{"type":"string","format":"date-time"}}}}},"paths":{"/rest_api/v2/programs/earning/points/calculate":{"post":{"tags":["Programs"],"summary":"Calculate earning points","description":"Calculate points that would be earned for given products","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"products":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"quantity":{"type":"integer"},"price":{"type":"number"}}}},"shopifyCustomerId":{"type":"string"},"sourceName":{"type":"string","default":"web"}},"required":["products"]}}}},"responses":{"200":{"description":"Calculated points","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"pointsEarn":{"type":"integer"},"products":{"type":"array","items":{"type":"object"}}}}}}]}}}}}}}}}
```

## Handle social earning interactions

> Process social media interactions for earning points

```json
{"openapi":"3.0.0","info":{"title":"Joy Loyalty Program - REST API v2","version":"2.0.0"},"tags":[{"name":"Programs","description":"Earning and spending programs management"}],"servers":[{"url":"https://dev-api.joy.so","description":"Staging"},{"url":"https://api.joy.so","description":"Production"}],"security":[{"JoyAuth":[],"JoySecretAuth":[]}],"components":{"securitySchemes":{"JoyAuth":{"type":"apiKey","in":"header","name":"X-Joy-Loyalty-App-Key"}},"schemas":{"SuccessResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object"},"meta":{"type":"object","description":"Additional metadata such as counts and pagination"},"message":{"type":"string"},"timestamp":{"type":"string","format":"date-time"}}}}},"paths":{"/rest_api/v2/programs/earning/social/interactions":{"post":{"tags":["Programs"],"summary":"Handle social earning interactions","description":"Process social media interactions for earning points","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"shopifyCustomerId":{"type":"string"},"event":{"type":"string"},"earningId":{"type":"string"}},"required":["shopifyCustomerId"]}}}},"responses":{"200":{"description":"Social earning processed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}}}}}
```

## Get redemption programs

> Retrieve all point spending/redemption programs

```json
{"openapi":"3.0.0","info":{"title":"Joy Loyalty Program - REST API v2","version":"2.0.0"},"tags":[{"name":"Programs","description":"Earning and spending programs management"}],"servers":[{"url":"https://dev-api.joy.so","description":"Staging"},{"url":"https://api.joy.so","description":"Production"}],"security":[{"JoyAuth":[],"JoySecretAuth":[]}],"components":{"securitySchemes":{"JoyAuth":{"type":"apiKey","in":"header","name":"X-Joy-Loyalty-App-Key"}},"schemas":{"ListResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"type":"object"}},"meta":{"type":"object","properties":{"count":{"type":"integer","description":"Number of items returned"}}}}}]},"SuccessResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object"},"meta":{"type":"object","description":"Additional metadata such as counts and pagination"},"message":{"type":"string"},"timestamp":{"type":"string","format":"date-time"}}},"Program":{"type":"object","description":"Loyalty program (earning or spending) with all fields that may be returned by the API. Note: Actual responses may only include non-null fields due to automatic filtering.","properties":{"id":{"type":"string"},"title":{"type":"string"},"type":{"type":"string","enum":["earning","spending","tier_spending","tier"]},"event":{"type":"string"},"status":{"type":"boolean"},"priority":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"expired":{"type":"boolean"},"isDraft":{"type":"boolean"},"earnBy":{"type":"string","enum":["price","order"]},"rateMoney":{"type":"number"},"earnPoint":{"type":"integer"},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"autoRemovePoints":{"type":"boolean"},"appliedPlaceOrderTo":{"type":"string","enum":["all","vip-tier"]},"appliedSource":{"type":"array","items":{"type":"string"}},"translateTitle":{"type":"object"},"typeMilestone":{"type":"string"},"milestones":{"type":"array","items":{"type":"object"}},"spendPoint":{"type":"integer"},"earnAmount":{"type":"number"},"redeemType":{"type":"string"},"appliedTo":{"type":"string"},"appliedCollectionIds":{"type":"array","items":{"type":"string"}},"redeemIn":{"type":"string","enum":["available_in_pos","available_in_online_store"]},"orderReq":{"type":"string","enum":["none","min_amount"]},"orderReqAmount":{"type":"number"},"minSpendPoint":{"type":"string"},"maxSpendPoint":{"type":"string"},"expiredTime":{"type":"string"},"userAvailability":{"type":"string","enum":["allUsers","userRedeemed"]},"showLoyaltyPage":{"type":"boolean"},"limitRedeem":{"type":"string","enum":["redeemWithoutLimit","redeemLimit"]},"totalLimitationRedeem":{"type":"integer"},"combinedWith":{"type":"array","items":{"type":"string"}},"specificProducts":{"type":"array","items":{"type":"object"}},"specificProductIds":{"type":"array","items":{"type":"string"}},"specificCollections":{"type":"array","items":{"type":"object"}},"variantIds":{"type":"array","items":{"type":"string"}},"freeProductIds":{"type":"array","items":{"type":"string"}},"giftStatus":{"type":"string","enum":["none","hot","expiring-soon"]},"excludeProducts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"image":{"type":"object","properties":{"src":{"type":"string"}}}}}},"includeProducts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"image":{"type":"object","properties":{"src":{"type":"string"}}}}}},"conditions":{"type":"array","items":{"type":"object"}},"earnPointsTiers":{"type":"array","items":{"type":"object","properties":{"earnPoint":{"type":"integer"},"rateMoney":{"type":"number"}}}},"roundingMethod":{"type":"string","enum":["round","floor","ceil"]},"skipEarnPointGuest":{"type":"boolean"}}}}},"paths":{"/rest_api/v2/programs/redemption":{"get":{"tags":["Programs"],"summary":"Get redemption programs","description":"Retrieve all point spending/redemption programs","parameters":[{"name":"shopifyCustomerId","in":"query","schema":{"type":"string"},"description":"Shopify customer ID for program limitations"},{"name":"event","in":"query","schema":{"type":"string","enum":["amount_discount","percentage_discount","free_shipping","free_gift","buy_x_get_y","custom_voucher"]},"description":"Filter by program event type"}],"responses":{"200":{"description":"List of spending programs","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ListResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Program"}}}}]}}}}}}}}}
```

## Create spending/redemption program

> Create a new spending/redemption program. The \`event\` field determines the redemption type.\
> \
> \### Event Types & Required Fields\
> \
> \*\*\`amount\_discount\` / \`percentage\_discount\`:\*\*\
> \- Required: \`title\`, \`event\`, \`spendPoint\` (positive int), \`earnAmount\` (discount value; for percentage must be < 100)\
> \- Optional: \`redeemType\` (fixed|dynamic, default: "fixed"). Dynamic mode (amount\_discount only) lets customer choose points to spend within min/max range.\
> \- Dynamic mode fields:\
> &#x20; \- \`hasMinSpend\`, \`minSpendPoint\` (must be divisible by spendPoint)\
> &#x20; \- \`hasMaxSpend\`, \`maxSpendPoint\` (must be >= spendPoint, divisible by spendPoint)\
> &#x20; \- \`hasLimitCartValue\`, \`limitCartValue\` (1-100, max % of cart)\
> \- Product scope (\`appliedTo\`):\
> &#x20; \- \`all\` — no extra fields\
> &#x20; \- \`specific\` or \`exclude\_collection\` — requires \`appliedCollectionIds\`\
> &#x20; \- \`sf\_product\` — requires \`specificProducts\` (max 100)\
> \- Order requirements (\`orderReq\`):\
> &#x20; \- \`none\` — no requirement\
> &#x20; \- \`min\_amount\` — requires \`orderReqAmount\`\
> &#x20; \- \`min\_quantity\` — requires \`orderReqQuantity\`\
> \- Expiration: \`expiredAfter\` (permanent|7D|14D|1M|3M|6M|1Y|2Y|specific). When "specific": \`expiredTime\` required.\
> \- Combination: \`combinedWith\` (array of orderDiscounts|productDiscounts|shippingDiscounts).\
> \- Availability: \`userAvailability\` (allUsers|userRedeemed|userInSegment). When "userInSegment": \`appliedSegments\` required.\
> \- Channel: \`appliedDiscountToSaleChannel\` (applyOnlineStore|applyPos|applyAll).\
> \
> \*\*\`free\_shipping\`:\*\*\
> \- Required: \`title\`, \`event\`, \`spendPoint\`, \`shippingDiscountType\` (percentage|fixedAmount), \`shippingDiscountAmount\` (for percentage, must be <= 100)\
> \- Optional: \`hasLimitShipping\`, \`limitShipping\` (max shipping cost), \`destination\` (all|countries), \`selectedCountries\`.\
> \- Note: \`combinedWith\` cannot include "shippingDiscounts" (this IS a shipping discount).\
> \
> \*\*\`free\_gift\`:\*\*\
> \- Required: \`title\`, \`event\`, \`spendPoint\`, \`appliedTo\` (sf\_product|specific|exclude\_collection — "all" NOT allowed)\
> \- Scope depends on \`appliedTo\`:\
> &#x20; \- \`sf\_product\` — requires \`specificProducts\` (1-100 items)\
> &#x20; \- \`specific\` or \`exclude\_collection\` — requires \`appliedCollectionIds\` + \`collectionQuantity\`\
> \- Optional: \`variantIds\`, \`freeGiftPriceSelection\` (highest|lowest), \`freeProductQuantity\` (all|one).\
> \
> \*\*\`buy\_x\_get\_y\`:\*\*\
> \- Required: \`title\`, \`event\`, \`spendPoint\`\
> \- Buy-side:\
> &#x20; \- \`orderReqQuantity\` (buy X items)\
> &#x20; \- \`appliedTo\` (specific | sf\_product — "all" NOT allowed)\
> &#x20; \- specific → \`appliedCollectionIds\` required\
> &#x20; \- sf\_product → \`specificProducts\` required\
> \- Get-side:\
> &#x20; \- \`orderGetQuantity\` (get Y items)\
> &#x20; \- \`appliedToGets\` (specific | sf\_product)\
> &#x20; \- specific → \`appliedCollectionIdsGets\` required\
> &#x20; \- sf\_product → \`specificProductsGets\` required\
> \- \`discountedType\` (percentage | amount | free). When percentage/amount → \`discountedValue\` required.\
> \
> \*\*\`custom\_voucher\`:\*\* (hidden feature, not recommended)\
> \- Required: \`title\`, \`event\`, \`spendPoint\`\
> \- Optional: \`descriptionHtml\`, \`imageUrl\`, \`newVouchers\` (array of voucher codes), \`voucherLimitRedeem\`.\
> \
> \### Shared Optional Fields\
> \- \`status\` (default: true), \`priority\`, \`isDraft\`, \`prefix\`, \`isUsePrefixDiscountCode\`, \`showLoyaltyPage\`\
> \- \`hasLimit\`, \`limitUnit\`, \`limitInterval\` — redemption frequency cap\
> \- \`usageLimit\`, \`appliesOncePerCustomer\` — Shopify discount usage limits\
> \- \`customerEligibility\` (eligible\_all|eligible\_tier\_customer), \`tiersEligible\`<br>

```json
{"openapi":"3.0.0","info":{"title":"Joy Loyalty Program - REST API v2","version":"2.0.0"},"tags":[{"name":"Programs","description":"Earning and spending programs management"}],"servers":[{"url":"https://dev-api.joy.so","description":"Staging"},{"url":"https://api.joy.so","description":"Production"}],"security":[{"JoyAuth":[],"JoySecretAuth":[]}],"components":{"securitySchemes":{"JoyAuth":{"type":"apiKey","in":"header","name":"X-Joy-Loyalty-App-Key"}},"schemas":{"SuccessResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object"},"meta":{"type":"object","description":"Additional metadata such as counts and pagination"},"message":{"type":"string"},"timestamp":{"type":"string","format":"date-time"}}},"Program":{"type":"object","description":"Loyalty program (earning or spending) with all fields that may be returned by the API. Note: Actual responses may only include non-null fields due to automatic filtering.","properties":{"id":{"type":"string"},"title":{"type":"string"},"type":{"type":"string","enum":["earning","spending","tier_spending","tier"]},"event":{"type":"string"},"status":{"type":"boolean"},"priority":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"expired":{"type":"boolean"},"isDraft":{"type":"boolean"},"earnBy":{"type":"string","enum":["price","order"]},"rateMoney":{"type":"number"},"earnPoint":{"type":"integer"},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"autoRemovePoints":{"type":"boolean"},"appliedPlaceOrderTo":{"type":"string","enum":["all","vip-tier"]},"appliedSource":{"type":"array","items":{"type":"string"}},"translateTitle":{"type":"object"},"typeMilestone":{"type":"string"},"milestones":{"type":"array","items":{"type":"object"}},"spendPoint":{"type":"integer"},"earnAmount":{"type":"number"},"redeemType":{"type":"string"},"appliedTo":{"type":"string"},"appliedCollectionIds":{"type":"array","items":{"type":"string"}},"redeemIn":{"type":"string","enum":["available_in_pos","available_in_online_store"]},"orderReq":{"type":"string","enum":["none","min_amount"]},"orderReqAmount":{"type":"number"},"minSpendPoint":{"type":"string"},"maxSpendPoint":{"type":"string"},"expiredTime":{"type":"string"},"userAvailability":{"type":"string","enum":["allUsers","userRedeemed"]},"showLoyaltyPage":{"type":"boolean"},"limitRedeem":{"type":"string","enum":["redeemWithoutLimit","redeemLimit"]},"totalLimitationRedeem":{"type":"integer"},"combinedWith":{"type":"array","items":{"type":"string"}},"specificProducts":{"type":"array","items":{"type":"object"}},"specificProductIds":{"type":"array","items":{"type":"string"}},"specificCollections":{"type":"array","items":{"type":"object"}},"variantIds":{"type":"array","items":{"type":"string"}},"freeProductIds":{"type":"array","items":{"type":"string"}},"giftStatus":{"type":"string","enum":["none","hot","expiring-soon"]},"excludeProducts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"image":{"type":"object","properties":{"src":{"type":"string"}}}}}},"includeProducts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"image":{"type":"object","properties":{"src":{"type":"string"}}}}}},"conditions":{"type":"array","items":{"type":"object"}},"earnPointsTiers":{"type":"array","items":{"type":"object","properties":{"earnPoint":{"type":"integer"},"rateMoney":{"type":"number"}}}},"roundingMethod":{"type":"string","enum":["round","floor","ceil"]},"skipEarnPointGuest":{"type":"boolean"}}},"ErrorResponse":{"type":"object","properties":{"success":{"type":"boolean"},"error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string"},"statusCode":{"type":"integer"},"details":{"type":"object"}}},"timestamp":{"type":"string","format":"date-time"}}}}},"paths":{"/rest_api/v2/programs/redemption":{"post":{"tags":["Programs"],"summary":"Create spending/redemption program","description":"Create a new spending/redemption program. The `event` field determines the redemption type.\n\n### Event Types & Required Fields\n\n**`amount_discount` / `percentage_discount`:**\n- Required: `title`, `event`, `spendPoint` (positive int), `earnAmount` (discount value; for percentage must be < 100)\n- Optional: `redeemType` (fixed|dynamic, default: \"fixed\"). Dynamic mode (amount_discount only) lets customer choose points to spend within min/max range.\n- Dynamic mode fields:\n  - `hasMinSpend`, `minSpendPoint` (must be divisible by spendPoint)\n  - `hasMaxSpend`, `maxSpendPoint` (must be >= spendPoint, divisible by spendPoint)\n  - `hasLimitCartValue`, `limitCartValue` (1-100, max % of cart)\n- Product scope (`appliedTo`):\n  - `all` — no extra fields\n  - `specific` or `exclude_collection` — requires `appliedCollectionIds`\n  - `sf_product` — requires `specificProducts` (max 100)\n- Order requirements (`orderReq`):\n  - `none` — no requirement\n  - `min_amount` — requires `orderReqAmount`\n  - `min_quantity` — requires `orderReqQuantity`\n- Expiration: `expiredAfter` (permanent|7D|14D|1M|3M|6M|1Y|2Y|specific). When \"specific\": `expiredTime` required.\n- Combination: `combinedWith` (array of orderDiscounts|productDiscounts|shippingDiscounts).\n- Availability: `userAvailability` (allUsers|userRedeemed|userInSegment). When \"userInSegment\": `appliedSegments` required.\n- Channel: `appliedDiscountToSaleChannel` (applyOnlineStore|applyPos|applyAll).\n\n**`free_shipping`:**\n- Required: `title`, `event`, `spendPoint`, `shippingDiscountType` (percentage|fixedAmount), `shippingDiscountAmount` (for percentage, must be <= 100)\n- Optional: `hasLimitShipping`, `limitShipping` (max shipping cost), `destination` (all|countries), `selectedCountries`.\n- Note: `combinedWith` cannot include \"shippingDiscounts\" (this IS a shipping discount).\n\n**`free_gift`:**\n- Required: `title`, `event`, `spendPoint`, `appliedTo` (sf_product|specific|exclude_collection — \"all\" NOT allowed)\n- Scope depends on `appliedTo`:\n  - `sf_product` — requires `specificProducts` (1-100 items)\n  - `specific` or `exclude_collection` — requires `appliedCollectionIds` + `collectionQuantity`\n- Optional: `variantIds`, `freeGiftPriceSelection` (highest|lowest), `freeProductQuantity` (all|one).\n\n**`buy_x_get_y`:**\n- Required: `title`, `event`, `spendPoint`\n- Buy-side:\n  - `orderReqQuantity` (buy X items)\n  - `appliedTo` (specific | sf_product — \"all\" NOT allowed)\n  - specific → `appliedCollectionIds` required\n  - sf_product → `specificProducts` required\n- Get-side:\n  - `orderGetQuantity` (get Y items)\n  - `appliedToGets` (specific | sf_product)\n  - specific → `appliedCollectionIdsGets` required\n  - sf_product → `specificProductsGets` required\n- `discountedType` (percentage | amount | free). When percentage/amount → `discountedValue` required.\n\n**`custom_voucher`:** (hidden feature, not recommended)\n- Required: `title`, `event`, `spendPoint`\n- Optional: `descriptionHtml`, `imageUrl`, `newVouchers` (array of voucher codes), `voucherLimitRedeem`.\n\n### Shared Optional Fields\n- `status` (default: true), `priority`, `isDraft`, `prefix`, `isUsePrefixDiscountCode`, `showLoyaltyPage`\n- `hasLimit`, `limitUnit`, `limitInterval` — redemption frequency cap\n- `usageLimit`, `appliesOncePerCustomer` — Shopify discount usage limits\n- `customerEligibility` (eligible_all|eligible_tier_customer), `tiersEligible`\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title","event"],"properties":{"title":{"type":"string","description":"Program display title shown to customers."},"event":{"type":"string","enum":["amount_discount","percentage_discount","free_shipping","free_gift","buy_x_get_y","custom_voucher"],"description":"Spending event type. Determines required fields and validation rules."},"status":{"type":"boolean","default":true,"description":"Whether the program is active."},"spendPoint":{"type":"integer","description":"Points required to redeem. Required for all events."},"earnAmount":{"type":"number","description":"Discount value. Required for amount_discount/percentage_discount. For percentage, must be < 100."},"redeemType":{"type":"string","enum":["fixed","dynamic"],"default":"fixed","description":"fixed: set points for set discount. dynamic: customer chooses within min/max range."},"shippingDiscountType":{"type":"string","enum":["percentage","fixedAmount"],"description":"Required for free_shipping."},"shippingDiscountAmount":{"type":"number","description":"Shipping discount value. Required for free_shipping. For percentage, must be <= 100."},"appliedTo":{"type":"string","enum":["all","specific","sf_product","exclude_collection"],"default":"all","description":"Product scope. For free_gift: 'all' NOT allowed."},"appliedCollectionIds":{"type":"array","items":{"type":"string"},"description":"Collection IDs. Required when appliedTo is 'specific' or 'exclude_collection'."},"specificProducts":{"type":"array","items":{"type":"object"},"description":"Product objects (max 100). Required when appliedTo is 'sf_product'."},"orderReq":{"type":"string","enum":["none","min_amount","min_quantity"],"default":"none","description":"Order requirement to use reward. 'min_amount': customer order must exceed orderReqAmount. 'min_quantity': order must have at least orderReqQuantity items."},"orderReqAmount":{"type":"number","description":"Min order amount. Required when orderReq='min_amount'."},"orderReqQuantity":{"type":"integer","description":"Min item quantity. Required when orderReq='min_quantity'. For buy_x_get_y: number of items customer must buy."},"orderGetQuantity":{"type":"integer","description":"Number of free/discounted items customer gets. Required for buy_x_get_y."},"appliedToGets":{"type":"string","enum":["specific","sf_product"],"description":"Get-side product scope for buy_x_get_y. 'specific': use appliedCollectionIdsGets. 'sf_product': use specificProductsGets."},"discountedType":{"type":"string","enum":["percentage","amount","free"],"description":"Discount applied to get-side items. 'free': 100% off. 'percentage'/'amount': requires discountedValue. Required for buy_x_get_y."},"discountedValue":{"type":"number","description":"Discount value on get-side items. Required when discountedType is 'percentage' or 'amount'."},"expiredAfter":{"type":"string","enum":["permanent","7D","14D","1M","3M","6M","1Y","2Y","specific"],"default":"permanent","description":"Coupon expiration period. 'permanent': never expires. 'specific': use expiredTime for exact datetime."},"combinedWith":{"type":"array","items":{"type":"string","enum":["orderDiscounts","productDiscounts","shippingDiscounts"]},"description":"Allow combining with other Shopify discount types. For free_shipping: cannot include 'shippingDiscounts'."},"appliedDiscountToSaleChannel":{"type":"string","enum":["applyOnlineStore","applyPos","applyAll"],"default":"applyOnlineStore","description":"Which sales channels the coupon is valid on."},"userAvailability":{"type":"string","enum":["allUsers","userRedeemed","userInSegment"],"default":"userRedeemed","description":"Who can redeem. 'allUsers': everyone. 'userRedeemed': only loyalty members. 'userInSegment': requires appliedSegments."},"priority":{"type":"integer","description":"Program display order. Lower = higher priority."},"isDraft":{"type":"boolean","description":"Save as draft without activating."},"prefix":{"type":"string","description":"Discount code prefix."},"isUsePrefixDiscountCode":{"type":"boolean","description":"Whether to use prefix for discount codes."},"showLoyaltyPage":{"type":"boolean","description":"Show on loyalty/rewards page."},"expiredTime":{"type":"string","description":"Specific expiration datetime. Required when expiredAfter='specific'."},"purchaseType":{"type":"string","enum":["one_time_purchase","subscription_purchase","both_purchase"],"default":"one_time_purchase","description":"Which purchase types the discount applies to. Only visible when shop.enableDiscountSubscription is enabled. For amount_discount/percentage_discount."},"conditions":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["customer","order"]},"typeMatch":{"type":"string","enum":["includes","not_includes","equal","not_equal","starts_with","ends_with","empty","not_empty"]},"field":{"type":"string","enum":["customer_country","customer_email","customer_phone","customer_join_date","order_shipping_method"]},"content":{"oneOf":[{"type":"array"},{"type":"string"}]}}},"description":"Advanced discount conditions. Customer-level conditions for all events; order-level (order_shipping_method) only for free_shipping. Requires Advanced plan."},"isRedeemCheckOut":{"type":"boolean","default":false,"description":"Allow checkout-page redemption. Requires Shopify Plus and Enterprise/Pro plan."},"enabledExcludePOS":{"type":"boolean","default":false,"description":"Exclude specific POS locations."},"excludeLocationsPOS":{"type":"array","items":{"type":"string"},"description":"POS location IDs to exclude."},"showChannel":{"type":"string","enum":["online_store","pos","all"],"default":"online_store","description":"Where the program is displayed. For POS-enabled programs."},"isAppliedVipTier":{"type":"boolean","default":false,"description":"Restrict to specific VIP tiers. For amount_discount/percentage_discount."},"appliedTiers":{"type":"array","items":{"type":"string"},"description":"Tier IDs that can access this program. Used when isAppliedVipTier=true."},"earnPointsTiers":{"type":"object","description":"Per-tier overrides for spend/earn values. Format: { [tierId]: { earnPoint, spendPoint, earnAmount } }. Used when isAppliedVipTier=true."},"hasMinSpend":{"type":"boolean","default":false,"description":"Enable minimum spend in dynamic mode. For amount_discount with redeemType='dynamic'."},"minSpendPoint":{"type":"integer","description":"Min points to spend (must be divisible by spendPoint). Required when hasMinSpend=true."},"hasMaxSpend":{"type":"boolean","default":false,"description":"Enable maximum spend in dynamic mode."},"maxSpendPoint":{"type":"integer","description":"Max points (must be >= spendPoint, divisible by spendPoint). Required when hasMaxSpend=true."},"hasLimitCartValue":{"type":"boolean","default":false,"description":"Limit discount to percentage of cart value."},"limitCartValue":{"type":"integer","minimum":1,"maximum":100,"description":"Max percentage of cart value for discount (1-100). Required when hasLimitCartValue=true."},"blockLimitedCouponReuse":{"type":"boolean","default":false,"description":"Prevent reuse of cart-value-limited coupons on smaller carts. Recommended when hasLimitCartValue=true."},"hasLimitShipping":{"type":"boolean","default":false,"description":"Apply only to shipping rates below max cost. For free_shipping."},"limitShipping":{"type":"integer","description":"Max shipping cost. Required when hasLimitShipping=true."},"destination":{"type":"string","enum":["all","countries"],"default":"all","description":"Shipping destination scope. For free_shipping."},"selectedCountries":{"type":"array","items":{"type":"object"},"description":"Country filter. Required when destination='countries'."},"specificProductIds":{"type":"array","items":{"type":"string"},"description":"Product variant IDs for the buy side. Required when appliedTo='sf_product' for buy_x_get_y."},"specificCollections":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"}}},"description":"Collection objects (for display). Used alongside appliedCollectionIds."},"variantIds":{"type":"array","items":{"type":"string"},"description":"Specific variant IDs (max 100). For free_gift with appliedTo='sf_product'."},"freeGiftPriceSelection":{"type":"string","enum":["highest","lowest"],"default":"highest","description":"Which product price to select from collection. For free_gift with appliedTo='specific'/'exclude_collection'."},"freeProductQuantity":{"type":"string","enum":["all","one"],"default":"all","description":"Whether customer gets all listed products or picks one. For free_gift."},"collectionQuantity":{"type":"integer","minimum":1,"description":"Total quantity of free products across collections. Required for free_gift when appliedTo='specific'/'exclude_collection'."},"appliedCollectionIdsGets":{"type":"array","items":{"type":"string"},"description":"Get-side collection IDs. Required when appliedToGets='specific'."},"specificProductsGets":{"type":"array","items":{"type":"object"},"description":"Get-side products (max 100). Required when appliedToGets='sf_product'."},"specificProductIdsGets":{"type":"array","items":{"type":"string"},"description":"Get-side product variant IDs. Required when appliedToGets='sf_product'."},"specificCollectionsGets":{"type":"array","items":{"type":"object"},"description":"Get-side collection objects (for display). Used alongside appliedCollectionIdsGets."},"enableMaximumUsesPerOrder":{"type":"boolean","default":false,"description":"Cap uses per order. For buy_x_get_y."},"maximumUsesPerOrder":{"type":"integer","description":"Max uses per order. Required when enableMaximumUsesPerOrder=true."},"descriptionHtml":{"type":"string","description":"Rich-text HTML description shown to customers. For custom_voucher."},"imageUrl":{"type":"string","nullable":true,"description":"Voucher image URL. For custom_voucher."},"newVouchers":{"type":"array","items":{"type":"string"},"description":"Voucher codes to import (duplicates auto-removed, existing codes skipped). For custom_voucher."},"voucherLimitRedeem":{"type":"integer","minimum":1,"nullable":true,"description":"Max vouchers a single customer can redeem. null/omit = unlimited. For custom_voucher."},"expDateFrom":{"type":"string","nullable":true,"description":"Start date for voucher redemption period (ISO 8601). For custom_voucher."},"expDateTo":{"type":"string","nullable":true,"description":"End date for voucher redemption period (ISO 8601). Must be after expDateFrom. For custom_voucher."},"appliedSegments":{"type":"array","items":{"type":"object"},"description":"Segment filter. Required when userAvailability='userInSegment'."},"usageLimit":{"type":"integer","description":"Shopify discount usage limit."},"appliesOncePerCustomer":{"type":"boolean","description":"Limit to one use per customer."},"customerEligibility":{"type":"string","enum":["eligible_all","eligible_tier_customer"],"default":"eligible_all","description":"Customer eligibility filter."},"tiersEligible":{"type":"array","items":{"type":"string"},"description":"Tier IDs. Required when customerEligibility='eligible_tier_customer'."},"hasLimit":{"type":"boolean","default":false,"description":"Enable redemption frequency cap."},"limitUnit":{"type":"string","enum":["minute","hour","day","week","month","year","lifetime"],"default":"lifetime","description":"Time unit for frequency cap."},"limitInterval":{"type":"integer","description":"Number of limitUnit periods between redemptions. Required when hasLimit=true AND limitUnit != 'lifetime'."},"limitRedeem":{"type":"string","enum":["redeemWithoutLimit","redeemLimit"],"description":"Global redemption limit type."},"totalLimitationRedeem":{"type":"integer","description":"Total redemption limit. Required when limitRedeem='redeemLimit'."}}}}}},"responses":{"201":{"description":"Program created successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Program"}}}]}}}},"400":{"description":"Validation error — missing required fields, invalid values, or cross-field constraint violations","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Get program by ID

> Retrieve a specific program by its ID

```json
{"openapi":"3.0.0","info":{"title":"Joy Loyalty Program - REST API v2","version":"2.0.0"},"tags":[{"name":"Programs","description":"Earning and spending programs management"}],"servers":[{"url":"https://dev-api.joy.so","description":"Staging"},{"url":"https://api.joy.so","description":"Production"}],"security":[{"JoyAuth":[],"JoySecretAuth":[]}],"components":{"securitySchemes":{"JoyAuth":{"type":"apiKey","in":"header","name":"X-Joy-Loyalty-App-Key"}},"schemas":{"SuccessResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object"},"meta":{"type":"object","description":"Additional metadata such as counts and pagination"},"message":{"type":"string"},"timestamp":{"type":"string","format":"date-time"}}},"Program":{"type":"object","description":"Loyalty program (earning or spending) with all fields that may be returned by the API. Note: Actual responses may only include non-null fields due to automatic filtering.","properties":{"id":{"type":"string"},"title":{"type":"string"},"type":{"type":"string","enum":["earning","spending","tier_spending","tier"]},"event":{"type":"string"},"status":{"type":"boolean"},"priority":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"expired":{"type":"boolean"},"isDraft":{"type":"boolean"},"earnBy":{"type":"string","enum":["price","order"]},"rateMoney":{"type":"number"},"earnPoint":{"type":"integer"},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"autoRemovePoints":{"type":"boolean"},"appliedPlaceOrderTo":{"type":"string","enum":["all","vip-tier"]},"appliedSource":{"type":"array","items":{"type":"string"}},"translateTitle":{"type":"object"},"typeMilestone":{"type":"string"},"milestones":{"type":"array","items":{"type":"object"}},"spendPoint":{"type":"integer"},"earnAmount":{"type":"number"},"redeemType":{"type":"string"},"appliedTo":{"type":"string"},"appliedCollectionIds":{"type":"array","items":{"type":"string"}},"redeemIn":{"type":"string","enum":["available_in_pos","available_in_online_store"]},"orderReq":{"type":"string","enum":["none","min_amount"]},"orderReqAmount":{"type":"number"},"minSpendPoint":{"type":"string"},"maxSpendPoint":{"type":"string"},"expiredTime":{"type":"string"},"userAvailability":{"type":"string","enum":["allUsers","userRedeemed"]},"showLoyaltyPage":{"type":"boolean"},"limitRedeem":{"type":"string","enum":["redeemWithoutLimit","redeemLimit"]},"totalLimitationRedeem":{"type":"integer"},"combinedWith":{"type":"array","items":{"type":"string"}},"specificProducts":{"type":"array","items":{"type":"object"}},"specificProductIds":{"type":"array","items":{"type":"string"}},"specificCollections":{"type":"array","items":{"type":"object"}},"variantIds":{"type":"array","items":{"type":"string"}},"freeProductIds":{"type":"array","items":{"type":"string"}},"giftStatus":{"type":"string","enum":["none","hot","expiring-soon"]},"excludeProducts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"image":{"type":"object","properties":{"src":{"type":"string"}}}}}},"includeProducts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"image":{"type":"object","properties":{"src":{"type":"string"}}}}}},"conditions":{"type":"array","items":{"type":"object"}},"earnPointsTiers":{"type":"array","items":{"type":"object","properties":{"earnPoint":{"type":"integer"},"rateMoney":{"type":"number"}}}},"roundingMethod":{"type":"string","enum":["round","floor","ceil"]},"skipEarnPointGuest":{"type":"boolean"}}},"ErrorResponse":{"type":"object","properties":{"success":{"type":"boolean"},"error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string"},"statusCode":{"type":"integer"},"details":{"type":"object"}}},"timestamp":{"type":"string","format":"date-time"}}}}},"paths":{"/rest_api/v2/programs/{programId}":{"get":{"tags":["Programs"],"summary":"Get program by ID","description":"Retrieve a specific program by its ID","parameters":[{"name":"programId","in":"path","required":true,"schema":{"type":"string"},"description":"Program ID"},{"name":"shopifyCustomerId","in":"query","schema":{"type":"string"},"description":"Shopify customer ID for program limitations"}],"responses":{"200":{"description":"Program details","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Program"}}}]}}}},"404":{"description":"Program not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Update program

> Partial update — send only the fields you want to change. The endpoint looks up the program, determines its type (earning or spending), and filters the update body to only fields allowed for that type. Unrecognized or readonly fields are silently ignored.\
> \
> \### Behavior\
> 1\. \*\*Readonly fields stripped:\*\* \`id\`, \`type\`, \`shopId\`, \`createdAt\`, \`updatedAt\` are always ignored — you cannot change a program's type or ID.\
> 2\. \*\*Field whitelist applied:\*\* Only fields in \`earningProgramFields\` (for earning) or \`spendingProgramFields\` (for spending) pass through. Other fields are silently dropped.\
> 3\. \*\*Empty body rejected:\*\* Returns 400 \`UPDATE\_DATA\_REQUIRED\` if the request body is empty.\
> 4\. \*\*No valid fields rejected:\*\* Returns 400 \`INVALID\_UPDATE\_FIELDS\` if all provided fields were readonly or not in the whitelist.\
> 5\. \*\*Response:\*\* Returns the full updated program (re-fetched after save), with null fields filtered out and dates formatted.\
> \
> \### Updatable Fields by Program Type\
> \
> \*\*Earning programs\*\* — any field from \`earningProgramFields\` (see POST /programs/earning):\
> \
> \| Category | Fields |\
> \|----------|--------|\
> \| Basic | \`title\`, \`status\`, \`earnPoint\`, \`priority\`, \`isDraft\`, \`startDate\`, \`endDate\` |\
> \| Purchase | \`earnBy\`, \`rateMoney\`, \`autoRemovePoints\`, \`appliedPlaceOrderTo\`, \`appliedSource\`, \`excludeProducts\`, \`includeProducts\`, \`conditions\`, \`roundingMethod\`, \`skipEarnPointGuest\` |\
> \| Social | \`urlAccount\`, \`enabledAntiCheat\`, \`message\`, \`typeJoin\`, \`imageQRCode\` |\
> \| Review | \`reviewApp\`, \`descriptionReview\`, \`isAppliedExtraPoints\`, \`extraPoints\` |\
> \| Frequency | \`hasLimit\`, \`limitUnit\`, \`limitInterval\` |\
> \| VIP Tier | \`isAppliedVipTier\`, \`earnPointsTiers\` |\
> \| Discount (typeEarn != point) | \`prefix\`, \`isUsePrefixDiscountCode\`, \`appliedTo\`, \`appliedCollectionIds\`, \`specificProducts\`, \`orderReq\`, \`orderReqAmount\`, \`expiredAfter\`, \`expiredTime\`, \`combinedWith\`, \`appliedDiscountToSaleChannel\`, \`userAvailability\` |\
> \
> \
> \*\*Spending programs\*\* — any field from \`spendingProgramFields\` (see POST /programs/redemption):\
> \
> \| Category | Fields |\
> \|----------|--------|\
> \| Basic | \`title\`, \`status\`, \`spendPoint\`, \`earnAmount\`, \`redeemType\`, \`priority\`, \`isDraft\` |\
> \| Product scope | \`appliedTo\`, \`appliedCollectionIds\`, \`specificProducts\`, \`specificProductIds\`, \`specificCollections\`, \`variantIds\` |\
> \| Order requirements | \`orderReq\`, \`orderReqAmount\`, \`orderReqQuantity\` |\
> \| Dynamic mode | \`minSpendPoint\`, \`maxSpendPoint\`, \`hasMinSpend\`, \`hasMaxSpend\`, \`hasLimitCartValue\`, \`limitCartValue\` |\
> \| Expiration | \`expiredAfter\`, \`expiredTime\` |\
> \| Limits | \`limitRedeem\`, \`totalLimitationRedeem\`, \`usageLimit\`, \`appliesOncePerCustomer\`, \`blockLimitedCouponReuse\`, \`hasLimit\`, \`limitInterval\`, \`limitUnit\` |\
> \| Availability | \`userAvailability\`, \`appliedSegments\`, \`customerEligibility\`, \`tiersEligible\`, \`showLoyaltyPage\` |\
> \| Discount config | \`combinedWith\`, \`appliedDiscountToSaleChannel\`, \`purchaseType\`, \`prefix\`, \`isUsePrefixDiscountCode\`, \`isRedeemCheckOut\`, \`enabledExcludePOS\`, \`excludeLocationsPOS\` |\
> \
> \### Important Notes\
> \- You \*\*cannot change\*\* \`event\` or \`type\` — these are set at creation time.\
> \- No cross-field validation is applied on update (unlike create). The provided fields are saved as-is.\
> \- For spending programs, \`spendPoint\` is cast to integer in the response via \`prepareProgramData\`.<br>

```json
{"openapi":"3.0.0","info":{"title":"Joy Loyalty Program - REST API v2","version":"2.0.0"},"tags":[{"name":"Programs","description":"Earning and spending programs management"}],"servers":[{"url":"https://dev-api.joy.so","description":"Staging"},{"url":"https://api.joy.so","description":"Production"}],"security":[{"JoyAuth":[],"JoySecretAuth":[]}],"components":{"securitySchemes":{"JoyAuth":{"type":"apiKey","in":"header","name":"X-Joy-Loyalty-App-Key"}},"schemas":{"SuccessResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object"},"meta":{"type":"object","description":"Additional metadata such as counts and pagination"},"message":{"type":"string"},"timestamp":{"type":"string","format":"date-time"}}},"Program":{"type":"object","description":"Loyalty program (earning or spending) with all fields that may be returned by the API. Note: Actual responses may only include non-null fields due to automatic filtering.","properties":{"id":{"type":"string"},"title":{"type":"string"},"type":{"type":"string","enum":["earning","spending","tier_spending","tier"]},"event":{"type":"string"},"status":{"type":"boolean"},"priority":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"expired":{"type":"boolean"},"isDraft":{"type":"boolean"},"earnBy":{"type":"string","enum":["price","order"]},"rateMoney":{"type":"number"},"earnPoint":{"type":"integer"},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time"},"autoRemovePoints":{"type":"boolean"},"appliedPlaceOrderTo":{"type":"string","enum":["all","vip-tier"]},"appliedSource":{"type":"array","items":{"type":"string"}},"translateTitle":{"type":"object"},"typeMilestone":{"type":"string"},"milestones":{"type":"array","items":{"type":"object"}},"spendPoint":{"type":"integer"},"earnAmount":{"type":"number"},"redeemType":{"type":"string"},"appliedTo":{"type":"string"},"appliedCollectionIds":{"type":"array","items":{"type":"string"}},"redeemIn":{"type":"string","enum":["available_in_pos","available_in_online_store"]},"orderReq":{"type":"string","enum":["none","min_amount"]},"orderReqAmount":{"type":"number"},"minSpendPoint":{"type":"string"},"maxSpendPoint":{"type":"string"},"expiredTime":{"type":"string"},"userAvailability":{"type":"string","enum":["allUsers","userRedeemed"]},"showLoyaltyPage":{"type":"boolean"},"limitRedeem":{"type":"string","enum":["redeemWithoutLimit","redeemLimit"]},"totalLimitationRedeem":{"type":"integer"},"combinedWith":{"type":"array","items":{"type":"string"}},"specificProducts":{"type":"array","items":{"type":"object"}},"specificProductIds":{"type":"array","items":{"type":"string"}},"specificCollections":{"type":"array","items":{"type":"object"}},"variantIds":{"type":"array","items":{"type":"string"}},"freeProductIds":{"type":"array","items":{"type":"string"}},"giftStatus":{"type":"string","enum":["none","hot","expiring-soon"]},"excludeProducts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"image":{"type":"object","properties":{"src":{"type":"string"}}}}}},"includeProducts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"image":{"type":"object","properties":{"src":{"type":"string"}}}}}},"conditions":{"type":"array","items":{"type":"object"}},"earnPointsTiers":{"type":"array","items":{"type":"object","properties":{"earnPoint":{"type":"integer"},"rateMoney":{"type":"number"}}}},"roundingMethod":{"type":"string","enum":["round","floor","ceil"]},"skipEarnPointGuest":{"type":"boolean"}}},"ErrorResponse":{"type":"object","properties":{"success":{"type":"boolean"},"error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string"},"statusCode":{"type":"integer"},"details":{"type":"object"}}},"timestamp":{"type":"string","format":"date-time"}}}}},"paths":{"/rest_api/v2/programs/{programId}":{"put":{"tags":["Programs"],"summary":"Update program","description":"Partial update — send only the fields you want to change. The endpoint looks up the program, determines its type (earning or spending), and filters the update body to only fields allowed for that type. Unrecognized or readonly fields are silently ignored.\n\n### Behavior\n1. **Readonly fields stripped:** `id`, `type`, `shopId`, `createdAt`, `updatedAt` are always ignored — you cannot change a program's type or ID.\n2. **Field whitelist applied:** Only fields in `earningProgramFields` (for earning) or `spendingProgramFields` (for spending) pass through. Other fields are silently dropped.\n3. **Empty body rejected:** Returns 400 `UPDATE_DATA_REQUIRED` if the request body is empty.\n4. **No valid fields rejected:** Returns 400 `INVALID_UPDATE_FIELDS` if all provided fields were readonly or not in the whitelist.\n5. **Response:** Returns the full updated program (re-fetched after save), with null fields filtered out and dates formatted.\n\n### Updatable Fields by Program Type\n\n**Earning programs** — any field from `earningProgramFields` (see POST /programs/earning):\n\n| Category | Fields |\n|----------|--------|\n| Basic | `title`, `status`, `earnPoint`, `priority`, `isDraft`, `startDate`, `endDate` |\n| Purchase | `earnBy`, `rateMoney`, `autoRemovePoints`, `appliedPlaceOrderTo`, `appliedSource`, `excludeProducts`, `includeProducts`, `conditions`, `roundingMethod`, `skipEarnPointGuest` |\n| Social | `urlAccount`, `enabledAntiCheat`, `message`, `typeJoin`, `imageQRCode` |\n| Review | `reviewApp`, `descriptionReview`, `isAppliedExtraPoints`, `extraPoints` |\n| Frequency | `hasLimit`, `limitUnit`, `limitInterval` |\n| VIP Tier | `isAppliedVipTier`, `earnPointsTiers` |\n| Discount (typeEarn != point) | `prefix`, `isUsePrefixDiscountCode`, `appliedTo`, `appliedCollectionIds`, `specificProducts`, `orderReq`, `orderReqAmount`, `expiredAfter`, `expiredTime`, `combinedWith`, `appliedDiscountToSaleChannel`, `userAvailability` |\n\n\n**Spending programs** — any field from `spendingProgramFields` (see POST /programs/redemption):\n\n| Category | Fields |\n|----------|--------|\n| Basic | `title`, `status`, `spendPoint`, `earnAmount`, `redeemType`, `priority`, `isDraft` |\n| Product scope | `appliedTo`, `appliedCollectionIds`, `specificProducts`, `specificProductIds`, `specificCollections`, `variantIds` |\n| Order requirements | `orderReq`, `orderReqAmount`, `orderReqQuantity` |\n| Dynamic mode | `minSpendPoint`, `maxSpendPoint`, `hasMinSpend`, `hasMaxSpend`, `hasLimitCartValue`, `limitCartValue` |\n| Expiration | `expiredAfter`, `expiredTime` |\n| Limits | `limitRedeem`, `totalLimitationRedeem`, `usageLimit`, `appliesOncePerCustomer`, `blockLimitedCouponReuse`, `hasLimit`, `limitInterval`, `limitUnit` |\n| Availability | `userAvailability`, `appliedSegments`, `customerEligibility`, `tiersEligible`, `showLoyaltyPage` |\n| Discount config | `combinedWith`, `appliedDiscountToSaleChannel`, `purchaseType`, `prefix`, `isUsePrefixDiscountCode`, `isRedeemCheckOut`, `enabledExcludePOS`, `excludeLocationsPOS` |\n\n### Important Notes\n- You **cannot change** `event` or `type` — these are set at creation time.\n- No cross-field validation is applied on update (unlike create). The provided fields are saved as-is.\n- For spending programs, `spendPoint` is cast to integer in the response via `prepareProgramData`.\n","parameters":[{"name":"programId","in":"path","required":true,"schema":{"type":"string"},"description":"Program ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"Partial update object. Only include fields to change. Readonly fields (id, type, shopId, createdAt, updatedAt) are silently ignored.","properties":{"title":{"type":"string","description":"Program display title."},"status":{"type":"boolean","description":"Enable/disable the program."},"earnPoint":{"type":"number","description":"Points to award. For earning programs."},"spendPoint":{"type":"integer","description":"Points required to redeem. For spending programs."},"earnAmount":{"type":"number","description":"Discount value. For spending programs (amount_discount, percentage_discount)."},"priority":{"type":"integer","description":"Program display/sort priority."},"isDraft":{"type":"boolean","description":"Toggle draft mode."},"earnBy":{"type":"string","enum":["price","fixed_order","quantity","order"],"description":"Earning method. For place_order programs."},"rateMoney":{"type":"number","description":"Currency-to-points ratio. For place_order with earnBy=price/fixed_order."},"urlAccount":{"type":"string","description":"Social media URL. For social earning programs."},"startDate":{"type":"string","format":"date-time","description":"Program start date (ISO 8601). For earning programs."},"endDate":{"type":"string","format":"date-time","description":"Program end date (ISO 8601). For earning programs."},"autoRemovePoints":{"type":"boolean","description":"Remove points on refund. For place_order."},"roundingMethod":{"type":"string","enum":["roundingNearest","roundingUp","roundingDown"],"description":"Point rounding. For place_order."},"skipEarnPointGuest":{"type":"boolean","description":"Skip guest earning. For place_order."},"redeemType":{"type":"string","enum":["fixed","dynamic"],"description":"Redemption mode. For amount_discount."},"appliedTo":{"type":"string","description":"Product scope. For discount/free_gift programs."},"appliedCollectionIds":{"type":"array","items":{"type":"string"},"description":"Collection IDs. For programs with appliedTo=specific."},"specificProducts":{"type":"array","items":{"type":"object"},"description":"Product objects. For programs with appliedTo=sf_product."},"orderReq":{"type":"string","enum":["none","min_amount","min_quantity"],"description":"Order requirement."},"orderReqAmount":{"type":"number","description":"Minimum order amount."},"expiredAfter":{"type":"string","enum":["permanent","7D","14D","1M","3M","6M","1Y","2Y","specific"],"description":"Coupon expiration. For spending programs."},"expiredTime":{"type":"string","description":"Specific expiration datetime."},"combinedWith":{"type":"array","items":{"type":"string","enum":["orderDiscounts","productDiscounts","shippingDiscounts"]},"description":"Discount combination rules."},"appliedDiscountToSaleChannel":{"type":"string","enum":["applyOnlineStore","applyPos","applyAll"],"description":"Sales channel for discount."},"userAvailability":{"type":"string","enum":["allUsers","userRedeemed","userInSegment"],"description":"Who can use the discount."},"purchaseType":{"type":"string","enum":["one_time_purchase","subscription_purchase","both_purchase"],"description":"Purchase type filter."},"usageLimit":{"type":"integer","nullable":true,"description":"Shopify discount usage limit. null = unlimited."},"appliesOncePerCustomer":{"type":"boolean","description":"Limit to one use per customer."},"hasLimit":{"type":"boolean","description":"Enable earning/redeem frequency cap."},"limitUnit":{"type":"string","enum":["minute","hour","day","week","month","year","lifetime"],"description":"Frequency cap time unit."},"limitInterval":{"type":"integer","description":"Frequency cap interval."},"isAppliedVipTier":{"type":"boolean","description":"Enable VIP tier restrictions."},"earnPointsTiers":{"type":"object","description":"Per-tier earn/spend overrides."},"prefix":{"type":"string","description":"Discount code prefix."},"isUsePrefixDiscountCode":{"type":"boolean","description":"Whether to use prefix."},"isRedeemCheckOut":{"type":"boolean","description":"Allow checkout-page redemption."},"enabledExcludePOS":{"type":"boolean","description":"Exclude POS locations."},"excludeLocationsPOS":{"type":"array","items":{"type":"string"},"description":"POS location IDs to exclude."},"customerEligibility":{"type":"string","enum":["eligible_all","eligible_tier_customer"],"description":"Customer eligibility filter. For spending programs."},"tiersEligible":{"type":"array","items":{"type":"string"},"description":"Tier IDs for eligibility."},"conditions":{"type":"array","items":{"type":"object"},"description":"Advanced conditions."},"showLoyaltyPage":{"type":"boolean","description":"Show on loyalty page."},"hasMinSpend":{"type":"boolean","description":"Enable min spend (dynamic discount)."},"hasMaxSpend":{"type":"boolean","description":"Enable max spend (dynamic discount)."},"minSpendPoint":{"type":"integer","description":"Minimum points to spend."},"maxSpendPoint":{"type":"integer","description":"Maximum points to spend."},"hasLimitCartValue":{"type":"boolean","description":"Limit discount to % of cart."},"limitCartValue":{"type":"integer","description":"Max % of cart value (1-100)."},"blockLimitedCouponReuse":{"type":"boolean","description":"Prevent cart-limited coupon reuse."},"appliedSegments":{"type":"array","items":{"type":"object"},"description":"Customer segments for userInSegment."}}}}}},"responses":{"200":{"description":"Program updated successfully. Returns the full updated program.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Program"}}}]}}}},"400":{"description":"Invalid update data or no valid fields for program type","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Program not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Delete program (hard delete)

> Permanently removes a program from the database. This action cannot be undone.\
> \
> The response includes the full program data as it existed before deletion,\
> allowing the caller to store a backup if needed.<br>

```json
{"openapi":"3.0.0","info":{"title":"Joy Loyalty Program - REST API v2","version":"2.0.0"},"tags":[{"name":"Programs","description":"Earning and spending programs management"}],"servers":[{"url":"https://dev-api.joy.so","description":"Staging"},{"url":"https://api.joy.so","description":"Production"}],"security":[{"JoyAuth":[],"JoySecretAuth":[]}],"components":{"securitySchemes":{"JoyAuth":{"type":"apiKey","in":"header","name":"X-Joy-Loyalty-App-Key"}},"schemas":{"SuccessResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object"},"meta":{"type":"object","description":"Additional metadata such as counts and pagination"},"message":{"type":"string"},"timestamp":{"type":"string","format":"date-time"}}},"ErrorResponse":{"type":"object","properties":{"success":{"type":"boolean"},"error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string"},"statusCode":{"type":"integer"},"details":{"type":"object"}}},"timestamp":{"type":"string","format":"date-time"}}}}},"paths":{"/rest_api/v2/programs/{programId}":{"delete":{"tags":["Programs"],"summary":"Delete program (hard delete)","description":"Permanently removes a program from the database. This action cannot be undone.\n\nThe response includes the full program data as it existed before deletion,\nallowing the caller to store a backup if needed.\n","parameters":[{"name":"programId","in":"path","required":true,"schema":{"type":"string"},"description":"Program ID"}],"responses":{"200":{"description":"Program deleted successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","description":"Full program data as it existed before deletion","properties":{"id":{"type":"string"},"title":{"type":"string"},"event":{"type":"string"},"type":{"type":"string","enum":["earning","spending"]},"status":{"type":"boolean"}}}}}]}}}},"404":{"description":"Program not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Redeem points for rewards

> Redeem customer points for rewards through a redemption program

```json
{"openapi":"3.0.0","info":{"title":"Joy Loyalty Program - REST API v2","version":"2.0.0"},"tags":[{"name":"Programs","description":"Earning and spending programs management"}],"servers":[{"url":"https://dev-api.joy.so","description":"Staging"},{"url":"https://api.joy.so","description":"Production"}],"security":[{"JoyAuth":[],"JoySecretAuth":[]}],"components":{"securitySchemes":{"JoyAuth":{"type":"apiKey","in":"header","name":"X-Joy-Loyalty-App-Key"}},"schemas":{"SuccessResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object"},"meta":{"type":"object","description":"Additional metadata such as counts and pagination"},"message":{"type":"string"},"timestamp":{"type":"string","format":"date-time"}}}}},"paths":{"/rest_api/v2/programs/redemption/redeem":{"post":{"tags":["Programs"],"summary":"Redeem points for rewards","description":"Redeem customer points for rewards through a redemption program","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"programId":{"type":"string"},"shopifyCustomerId":{"type":"string"},"quantity":{"type":"integer","default":1}},"required":["programId","shopifyCustomerId"]}}}},"responses":{"200":{"description":"Redemption successful","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}}}}}
```
