@open-loyalty/mcp-server 1.3.7 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/instructions.d.ts +1 -1
- package/dist/instructions.js +18 -5
- package/dist/tools/reward/handlers.d.ts +2 -0
- package/dist/tools/reward/handlers.js +52 -6
- package/dist/tools/reward/index.d.ts +2 -0
- package/dist/tools/reward/index.js +13 -7
- package/dist/tools/reward/schemas.d.ts +2 -0
- package/dist/tools/reward/schemas.js +15 -5
- package/dist/tools/tierset.d.ts +1 -1
- package/dist/tools/tierset.js +49 -25
- package/dist/tools/transaction.js +5 -2
- package/dist/tools/wallet-type.js +26 -17
- package/dist/types/schemas/admin.d.ts +6 -6
- package/dist/types/schemas/role.d.ts +4 -4
- package/package.json +1 -1
- package/dist/prompts/fan-engagement-setup.d.ts +0 -107
- package/dist/prompts/fan-engagement-setup.js +0 -492
- package/dist/tools/achievement.d.ts +0 -1017
- package/dist/tools/achievement.js +0 -354
- package/dist/tools/campaign.d.ts +0 -1800
- package/dist/tools/campaign.js +0 -737
- package/dist/tools/member.d.ts +0 -366
- package/dist/tools/member.js +0 -352
- package/dist/tools/reward.d.ts +0 -279
- package/dist/tools/reward.js +0 -361
- package/dist/tools/segment.d.ts +0 -816
- package/dist/tools/segment.js +0 -333
- package/dist/workflows/app-login-streak.d.ts +0 -39
- package/dist/workflows/app-login-streak.js +0 -298
- package/dist/workflows/early-arrival.d.ts +0 -33
- package/dist/workflows/early-arrival.js +0 -148
- package/dist/workflows/index.d.ts +0 -101
- package/dist/workflows/index.js +0 -208
- package/dist/workflows/match-attendance.d.ts +0 -45
- package/dist/workflows/match-attendance.js +0 -308
- package/dist/workflows/sportsbar-visit.d.ts +0 -41
- package/dist/workflows/sportsbar-visit.js +0 -284
- package/dist/workflows/vod-watching.d.ts +0 -43
- package/dist/workflows/vod-watching.js +0 -326
package/dist/instructions.d.ts
CHANGED
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
* MCP Server Instructions - provides context and guidance for AI agents
|
|
3
3
|
* using the Open Loyalty MCP server.
|
|
4
4
|
*/
|
|
5
|
-
export declare const SERVER_INSTRUCTIONS = "\nOpen Loyalty MCP Server - Complete Loyalty Program Management\n\n## Domain Model\n\nMember (loyalty program participant)\n \u2514\u2500\u2500 Points (wallet balance, transfers)\n \u2514\u2500\u2500 Tier (current level: Bronze, Silver, Gold)\n \u2514\u2500\u2500 Transactions (purchase history)\n \u2514\u2500\u2500 Rewards (redeemed coupons)\n \u2514\u2500\u2500 Achievements (gamification progress)\n \u2514\u2500\u2500 Badges (visual rewards from achievements)\n\nTierSet (loyalty program structure)\n \u2514\u2500\u2500 Conditions (criteria: activeUnits, totalSpending)\n \u2514\u2500\u2500 Tiers (levels with thresholds)\n\nWalletType (points currency configuration)\n\nReward (redeemable items)\n \u2514\u2500\u2500 Categories (reward groupings)\n\nCampaign (automated points/rewards rules)\n \u2514\u2500\u2500 Type (earning, spending, custom, instant_reward)\n \u2514\u2500\u2500 Trigger (transaction, custom_event, points_transfer)\n \u2514\u2500\u2500 Rules (SKU, category, transaction amount conditions)\n \u2514\u2500\u2500 Effects (give_points, multiply_points, percentage_discount)\n \u2514\u2500\u2500 Targeting (segments, tiers for audience)\n\nSegment (member audience grouping)\n \u2514\u2500\u2500 Parts (groups of criteria - OR logic between parts)\n \u2514\u2500\u2500 Criteria (conditions - AND logic within part)\n Types: transaction_count (working; requires min+max). Other types may be rejected by the API.\n\nAchievement (gamification goals)\n \u2514\u2500\u2500 Trigger (transaction, custom_event, points_transfer, reward_redemption, referral, achievement, tier_change, profile_update)\n \u2514\u2500\u2500 CompleteRule (periodGoal, period type, unique counting)\n \u2514\u2500\u2500 Badge (visual reward when completed)\n\nBadge (visual recognition)\n \u2514\u2500\u2500 Linked to achievements via badgeTypeId\n \u2514\u2500\u2500 Auto-created when referenced by achievement\n\n## Key Workflows\n\n### 1. Create 3-tier Loyalty Program:\nRECOMMENDED WORKFLOW:\n1. wallet_type_list \u2192 Get wallet code (usually \"default\")\n2. tierset_list \u2192 Check if tier set already exists\n3. IF EXISTS: tierset_get \u2192 Get conditionId from existing tier set\n ELSE: tierset_create \u2192 Create new, then tierset_get to get conditionId\n4. tierset_update_tiers \u2192 Define tier thresholds using conditionId\n5. tierset_get_tiers \u2192 Verify tiers created correctly\n\nGOTCHA - Valid tier condition attributes:\n- activeUnits (current spendable balance)\n- totalEarnedUnits (lifetime points - NOT 'earnedUnits'!)\n- totalSpending (lifetime spend amount)\n- monthsSinceJoiningProgram (tenure in months)\n- cumulatedEarnedUnits (similar to totalEarnedUnits)\n\nCOMMON MISTAKE: Using 'earnedUnits' will fail - use 'totalEarnedUnits' for lifetime points.\n\n### 2. Register and Reward Member:\nmember_create \u2192 points_add (welcome bonus) \u2192 member_get (verify)\n\n### 3. Record Purchase and Earn Points:\ntransaction_create (with customerData) \u2192 triggers campaigns \u2192 points auto-added\n\n### 4. Redeem Reward:\nreward_list \u2192 reward_buy (deducts points) \u2192 reward_redeem (mark coupon used)\n\n### 5. Assign Unmatched Transaction:\ntransaction_list (matched=false) \u2192 transaction_assign_member \u2192 points earned\n\n### 6. Check Member Tier Progress:\nmember_get_tier_progress \u2192 shows current tier, next tier, progress %\n\n### 7. Double Points for VIP Members:\nsegment_create (with transaction_count criteria) \u2192 campaign_create (targeting that segment with give_points effect and pointsRule: \"transaction.grossValue * 2\")\n\n### 8. Purchase Achievement with Badge:\nbadge_list (find or note badgeTypeId) \u2192 achievement_create (type+trigger+aggregation+period required, badgeTypeId)\n\n### 9. Create Targeted Promotion:\nsegment_create (define audience) \u2192 campaign_create (type: earning, target: segment, segmentIds: [segmentId])\n\n### 10. Track Member Gamification:\nachievement_list_member_achievements \u2192 badge_get_member_badges \u2192 achievement_get_member_progress\n\n## Discovery Paths:\n- Members: member_list \u2192 member_get \u2192 points_get_balance\n- Tiers: tierset_list \u2192 tierset_get \u2192 tierset_get_tiers\n- Rewards: reward_category_list \u2192 reward_list \u2192 reward_get\n- Transactions: transaction_list \u2192 transaction_get\n- Campaigns: campaign_list \u2192 campaign_get \u2192 campaign_simulate\n- Segments: segment_list \u2192 segment_get \u2192 segment_get_members \u2192 campaign_create (audience targeting)\n- Achievements: achievement_list \u2192 badge_list \u2192 achievement_create (with badge)\n- Member Gamification: achievement_list_member_achievements \u2192 badge_get_member_badges\n\n## Important Patterns:\n- TIER SETUP: conditionId REQUIRED for tier thresholds\n 1. tierset_create returns conditions but you need the id from tierset_get\n 2. The conditionId is in tierset_get response: conditions[].id (this IS the conditionId)\n 3. Use this conditionId in tierset_update_tiers for each tier's threshold\n- TIER ATTRIBUTES: Use 'totalEarnedUnits' (NOT 'earnedUnits') for lifetime points\n- walletType uses CODE (e.g., 'default'), not UUID\n- Points operations: check balance with points_get_balance before spending\n- Reward flow: reward_buy returns couponCode, use reward_redeem to mark used\n- Transactions auto-match to members via customerData (email, phone, loyaltyCardNumber)\n\n### Pagination:\n- Traditional pagination: use page + perPage params\n- Cursor pagination: provide cursor from previous response for efficient deep pagination\n- Cursor-enabled tools: member_list, transaction_list, points_get_history\n- First scroll request: cursor=\"\" (empty string)\n- Subsequent requests: use cursor value from previous response\n- When cursor provided, page param is ignored\n\n### Campaign Patterns:\n- Campaign types: direct (standard), referral (referral programs)\n- Triggers: transaction (purchase), custom_event, time, achievement, etc.\n- Effects: give_points, give_reward, deduct_unit\n- Target campaigns to segments or tiers using audience.target: \"segment\" and audience.segments array\n- campaign_create requires activity.startsAt, rules[].name, and effects[].effect (use key effect, not type). pointsRule is a STRING expression.\n\n### Points Expression (pointsRule):\npointsRule is a STRING expression, not an object. Examples:\n- Fixed: \"100\"\n- Per dollar: \"transaction.grossValue * 10\"\n- Category-based: \"transaction.category('electronics').grossValue * 5\"\n- Capped: \"(transaction.grossValue * 2 >= 100) ? 100 : transaction.grossValue * 2\"\n- Rounded: \"round_up(0.1 * transaction.grossValue)\"\n\n### Segment Patterns:\n- Parts use OR logic (member matches ANY part)\n- Criteria within a part use AND logic (member must match ALL criteria in that part)\n- Common criteria: transaction_count (working; requires min+max). Other criteria may be rejected by the API.\n\n### Achievement Patterns:\n- Triggers: transaction, custom_event, points_transfer, reward_redemption, referral, achievement, tier_change, profile_update\n- periodGoal sets the target (e.g., 5 purchases, 1000 points spent)\n- period.type defines timeframe: day, week, month, year, last_day, calendarDays, calendarWeeks, calendarMonths, calendarYears\n- aggregation.type defines counting method: quantity (count events only)\n- Link to badge via badgeTypeId - badge auto-created if doesn't exist\n- uniqueAttribute for counting distinct values (e.g., unique products)\n\n### Achievement Period Types:\n- \"day\": Count all-time when consecutive=1\n- \"week\": Count per week (use consecutive=1 for all-time weekly tracking)\n- \"month\": Count per month\n- \"year\": Count per year\n- \"last_day\": Rolling window of N days (e.g., value: 7 for last 7 days)\n- \"calendarDays\": Reset daily at midnight\n- \"calendarWeeks\": Reset weekly (Monday)\n- \"calendarMonths\": Reset monthly (1st of month)\n- \"calendarYears\": Reset yearly (January 1st)\n\n### Streak Achievement Example (7-day login streak):\n{\n \"rules\": [{\n \"trigger\": \"custom_event\",\n \"event\": \"app_login\",\n \"type\": \"direct\",\n \"aggregation\": {\"type\": \"quantity\"},\n \"completeRule\": {\n \"periodGoal\": 1,\n \"period\": {\"type\": \"last_day\", \"value\": 7, \"consecutive\": 1}\n },\n \"limit\": {\n \"interval\": {\"type\": \"calendarDays\", \"value\": 1},\n \"value\": 1\n }\n }]\n}\n\n### Custom Event Workflows:\n\n#### Create Custom Event Campaign:\n1. custom_event_schema_list \u2192 Check if event type exists\n2. custom_event_schema_create (if needed) \u2192 Define event type and fields\n3. campaign_create with trigger: \"custom_event\", event: \"{event_type}\"\n4. Send events via custom_event_send to trigger campaign\n\n#### Create Achievement with Custom Event:\n1. custom_event_schema_create \u2192 Define event (e.g., \"location_visit\")\n2. achievement_create with:\n - rules[].trigger: \"custom_event\"\n - rules[].event: \"{event_type}\"\n - rules[].type: \"direct\"\n - rules[].aggregation.type: \"quantity\"\n - rules[].completeRule.periodGoal: {count}\n - rules[].completeRule.period: { type: \"day\", consecutive: 1 } (all-time)\n\n#### Create Campaign Triggered by Achievement:\n1. achievement_create \u2192 Get achievementId from response\n2. campaign_create with:\n - trigger: \"achievement\"\n - achievementId: \"{achievement_id}\"\n - rules with effects (give_points, give_reward)\n\n### Condition Operators (for campaigns/achievements):\n- is_equal, is_not_equal\n- gte, gt, lte, lt\n- is_between, is_not_between\n- is_time_between\n- is_day_of_week, is_day_of_month\n- contains, not_contains\n- starts_with, ends_with\n- one_of, not_one_of\n- expression (for custom logic)\n\n## Common Campaign Patterns (Industry-Agnostic)\n\nThese patterns work for any loyalty program - retail, QSR, aviation, fan engagement, hospitality, etc.\n\n### Pattern 1: Count Events Toward Goal (Achievements)\nUse case: Track member actions (visits, purchases, logins, check-ins) toward a milestone.\n1. custom_event_schema_create \u2192 Define your action type (e.g., \"store_visit\", \"app_login\")\n2. achievement_create with:\n - trigger: \"custom_event\"\n - event: \"{your_event_code}\"\n - aggregation: { type: \"quantity\" }\n - completeRule: { periodGoal: {count}, period: { type: \"day\", consecutive: 1 } }\n3. custom_event_send to log each action\n\n### Pattern 2: Points Per Action (Instant Rewards)\nUse case: Award instant points for each qualifying action.\n1. custom_event_schema_create (if not using transaction)\n2. campaign_create with:\n - trigger: \"custom_event\" (or \"transaction\")\n - event: \"{event_code}\" (for custom events)\n - rules: [{ effects: [{ effect: \"give_points\", pointsRule: \"50\" }] }]\n\n### Pattern 3: Conditional Rewards (Bonus Conditions)\nUse case: Award points only when a condition is met (e.g., early arrival, large purchase).\nUse ternary expressions in pointsRule instead of conditions array:\n- pointsRule: \"event.body.minutes_before >= 60 ? 25 : 0\"\n- pointsRule: \"transaction.grossValue >= 100 ? 50 : 0\"\n\n### Pattern 4: Execution Limits (Anti-Fraud)\nUse case: Limit rewards to prevent abuse (once per day, once per week).\nCampaign limits structure:\n{\n \"limits\": {\n \"executionsPerMember\": {\n \"value\": 1,\n \"interval\": { \"type\": \"calendarDays\", \"value\": 1 }\n }\n }\n}\nNote: Use \"calendarDays\" (not \"days\"), \"calendarWeeks\", \"calendarMonths\", \"calendarYears\".\nOmit interval entirely for lifetime/forever limit.\n\n### Pattern 5: Achievement-Triggered Bonus\nUse case: Award bonus points/rewards when member completes an achievement.\n1. achievement_create \u2192 Get achievementId from response\n2. campaign_create with:\n - trigger: \"achievement\"\n - achievementId: \"{achievement_id}\"\n - rules with give_points or give_reward effect\n\n### Pattern 6: Tiered Milestones\nUse case: Multiple achievement levels (bronze/silver/gold, 5/10/25 visits).\nCreate multiple achievements with increasing periodGoal values,\neach linked to a different bonus campaign with increasing rewards.\n\n### Pattern 7: Time-Limited Promotion\nUse case: Seasonal or promotional campaigns.\n{\n \"activity\": {\n \"startsAt\": \"2026-01-01 00:00+00:00\",\n \"endsAt\": \"2026-03-31 23:59+00:00\"\n }\n}\n\n### Pattern 8: Streak Tracking\nUse case: Reward consecutive daily/weekly actions (login streaks, workout streaks).\nAchievement with:\n- completeRule.period: { type: \"last_day\", value: 7 } (rolling 7-day window)\n- limit: { value: 1, interval: { type: \"calendarDays\", value: 1 } } (max 1 per day)\n\n## Phase 3: Analytics, Admin, Roles & Stores\n\n### Domain Model (Extended)\n\nAdmin (system user)\n \u2514\u2500\u2500 Roles (permission sets)\n \u2514\u2500\u2500 Permissions (resource + access level)\n \u2514\u2500\u2500 API Keys (programmatic access)\n\nStore (multi-tenant container)\n \u2514\u2500\u2500 Members, Campaigns, Rewards (isolated per store)\n\nAudit Log (compliance tracking)\n \u2514\u2500\u2500 eventType, entityType, entityId, username, timestamp\n\n### Analytics Workflows:\n\n#### 11. Get Program Overview:\nanalytics_dashboard \u2192 analytics_tiers \u2192 analytics_members\n\n#### 12. Analyze Points Economy:\nanalytics_points \u2192 analytics_units (per wallet) \u2192 points_get_histogram\n\n#### 13. Measure Campaign Performance:\nanalytics_campaigns \u2192 analytics_campaign_detail (specific campaign)\n\n#### 14. Track Transactions:\nanalytics_transactions \u2192 transaction_list (details)\n\n### Aggregation Queries (Top Spenders, Purchase Analysis):\n\nIMPORTANT: For queries like \"top 5 spenders in July 2025\", use this approach:\n\n#### 15. Find Top Spenders by Date Range:\n1. transaction_list(purchasedAtFrom, purchasedAtTo, perPage=50, cursor='') - returns customerId with each transaction\n2. Use cursor pagination to fetch ALL pages - even if there are 1000+ transactions\n3. Aggregate grossValue by customerId in your code\n4. Sort by total spent, take top N\n5. member_get for each top spender to get names/details\n\nCRITICAL - DO NOT TRY TO BE SMART OR OPTIMIZE:\n- ALWAYS iterate through ALL pages using cursor pagination - this is the ONLY correct approach\n- DO NOT skip pages or try to sample data - you will get inaccurate results\n- DO NOT use transaction_get individually - transaction_list already includes customerId\n- DO NOT try to find \"smarter\" analytics endpoints - they don't exist for per-customer aggregation\n- Large datasets (1000+ transactions) are normal - just keep paginating until cursor is empty\n- Start with cursor='' (empty string), use returned cursor for next request, repeat until done\n\nExample: Top 5 spenders July 2025\n- First request: transaction_list(purchasedAtFrom: \"2025-07-01\", purchasedAtTo: \"2025-07-31\", perPage: 50, cursor: \"\")\n- Each result includes: transactionId, grossValue, customerId, purchasedAt\n- Keep fetching with returned cursor until response has no cursor or empty transactions\n- Even if there are 1500 transactions (30 pages), iterate through ALL of them\n- Group by customerId, sum grossValue, sort descending, take 5\n\n### Admin & Security Workflows:\n\n#### 15. Create Admin with Limited Role:\nacl_get_resources \u2192 role_create \u2192 admin_create (with roleId)\n\n#### 16. Generate API Key for Integration:\nadmin_get \u2192 apikey_create \u2192 SAVE TOKEN IMMEDIATELY (shown once!)\n\n#### 17. Audit User Actions:\naudit_list (filter by username/entity) \u2192 audit_export (compliance report)\n\n### Store Multi-Tenancy:\n\n#### 18. Create New Store:\nstore_create \u2192 configure campaigns/tiers \u2192 member_create (with storeCode)\n\n### Analytics Query Patterns:\n- All analytics tools support dateFrom/dateTo ISO date filters\n- analytics_dashboard: high-level program metrics\n- analytics_units: wallet-specific metrics (requires walletTypeCode)\n- analytics_campaign_detail: detailed metrics for single campaign\n\n### Admin \u2192 Role \u2192 Permission Model:\n\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Admin \u2502\u2500\u2500\u2500\u2500\u25B6\u2502 Role \u2502\u2500\u2500\u2500\u2500\u25B6\u2502 Permission \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502 \u2502\n \u2502 resource + access\n \u2502 (VIEW, MODIFY, etc.)\n \u25BC\n\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 API Key \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\n### Store Multi-Tenancy:\n- Each store is isolated: members, campaigns, rewards, transactions\n- storeCode parameter routes requests to correct tenant\n- Default store used when storeCode omitted\n- IMPORTANT: Do NOT pass storeCode parameter unless the user explicitly asks to work with a different store. The configured default store should always be used unless the user requests otherwise.\n\n## Phase 4: Webhooks, Import & Export\n\n### Domain Model (Extended)\n\nWebhook Subscription (event notification)\n \u2514\u2500\u2500 eventName (event to subscribe to)\n \u2514\u2500\u2500 url (callback endpoint)\n \u2514\u2500\u2500 headers (custom HTTP headers)\n\nImport (bulk data upload)\n \u2514\u2500\u2500 type: member, groupValue, segmentMembers, unitTransferAdding, etc.\n \u2514\u2500\u2500 status: pending, processing, succeed, failed\n \u2514\u2500\u2500 items (individual row results)\n\nExport (bulk data download)\n \u2514\u2500\u2500 type: campaignCode, member, memberTier, memberSegment, rewardFulfillment\n \u2514\u2500\u2500 status: pending, done, failed, error\n \u2514\u2500\u2500 CSV file (when status='done')\n\n### Webhook Workflows:\n\n#### 19. Subscribe to Member Events for CRM Sync:\nwebhook_events \u2192 webhook_create (eventName: 'member.created', url: 'https://crm.example.com/webhook')\n\n#### 20. List and Manage Subscriptions:\nwebhook_list \u2192 webhook_get \u2192 webhook_update or webhook_delete\n\n### Import Workflows:\n\n#### 21. Bulk Import Members from CSV:\nimport_create (type: 'member', fileContent: CSV data) \u2192 import_list \u2192 import_get (check status)\n\n#### 22. Bulk Add Points to Members:\nimport_create (type: 'unitTransferAdding', fileContent: CSV) \u2192 poll import_get until complete\n\n### Export Workflows:\n\n#### 23. Export Campaign Codes:\nexport_create (type: 'campaignCode', filters: { campaignId }) \u2192 poll export_get (until status='done') \u2192 export_download\n\n#### 24. Export Member Data:\nexport_create (type: 'member') \u2192 export_get \u2192 export_download (returns CSV)\n\n### Webhook Patterns:\n- Use webhook_events to discover available event types before subscribing\n- API uses wrapper: { webhookSubscription: { eventName, url, headers? } }\n- Common events: member.created, member.updated, transaction.created, reward.purchased\n\n### Import Patterns:\n- Import is async: create returns importId, poll status with import_get\n- CSV format required - provide plain text, not base64\n- Types: member, groupValue, segmentMembers, unitTransferAdding, unitTransferSpending, transaction, campaignCode, rewardCoupon\n\n### Export Patterns:\n- Export is async: create returns exportId, poll status until 'done'\n- API body wrapper varies by type: { campaignCode: { filters... } }\n- Only call export_download when status='done'\n- Types: campaignCode, member, memberTier, memberSegment, rewardFulfillment\n";
|
|
5
|
+
export declare const SERVER_INSTRUCTIONS = "\nOpen Loyalty MCP Server - Complete Loyalty Program Management\n\n## Domain Model\n\nMember (loyalty program participant)\n \u2514\u2500\u2500 Points (wallet balance, transfers)\n \u2514\u2500\u2500 Tier (current level: Bronze, Silver, Gold)\n \u2514\u2500\u2500 Transactions (purchase history)\n \u2514\u2500\u2500 Rewards (redeemed coupons)\n \u2514\u2500\u2500 Achievements (gamification progress)\n \u2514\u2500\u2500 Badges (visual rewards from achievements)\n\nTierSet (loyalty program structure)\n \u2514\u2500\u2500 Conditions (criteria: activeUnits, totalSpending)\n \u2514\u2500\u2500 Tiers (levels with thresholds)\n\nWalletType (points currency configuration)\n\nReward (redeemable items)\n \u2514\u2500\u2500 Categories (reward groupings)\n\nCampaign (automated points/rewards rules)\n \u2514\u2500\u2500 Type (earning, spending, custom, instant_reward)\n \u2514\u2500\u2500 Trigger (transaction, custom_event, points_transfer)\n \u2514\u2500\u2500 Rules (SKU, category, transaction amount conditions)\n \u2514\u2500\u2500 Effects (give_points, multiply_points, percentage_discount)\n \u2514\u2500\u2500 Targeting (segments, tiers for audience)\n\nSegment (member audience grouping)\n \u2514\u2500\u2500 Parts (groups of criteria - OR logic between parts)\n \u2514\u2500\u2500 Criteria (conditions - AND logic within part)\n Types: transaction_count (working; requires min+max). Other types may be rejected by the API.\n\nAchievement (gamification goals)\n \u2514\u2500\u2500 Trigger (transaction, custom_event, points_transfer, reward_redemption, referral, achievement, tier_change, profile_update)\n \u2514\u2500\u2500 CompleteRule (periodGoal, period type, unique counting)\n \u2514\u2500\u2500 Badge (visual reward when completed)\n\nBadge (visual recognition)\n \u2514\u2500\u2500 Linked to achievements via badgeTypeId\n \u2514\u2500\u2500 Auto-created when referenced by achievement\n\n## Key Workflows\n\n### 1. Create Multi-tier Loyalty Program:\nCRITICAL CONCEPT: A tier set is a CONTAINER that holds ALL tiers (Bronze/Silver/Gold/etc).\n- Create exactly ONE tier set\n- Add ALL tiers to it in ONE call to tierset_update_tiers\n- DO NOT create multiple tier sets for different tiers!\n\nRECOMMENDED WORKFLOW:\n1. wallet_type_list \u2192 Get wallet code (usually \"default\")\n2. tierset_list \u2192 Check if tier set already exists\n3. IF EXISTS: tierset_get \u2192 Get conditionId from existing tier set\n ELSE: tierset_create \u2192 Create ONE tier set, then tierset_get to get conditionId\n4. tierset_update_tiers \u2192 Define ALL tier thresholds in ONE call:\n tiers: [\n { name: \"Bronze\", conditions: [{ conditionId: \"xxx\", value: 0 }] },\n { name: \"Silver\", conditions: [{ conditionId: \"xxx\", value: 500 }] },\n { name: \"Gold\", conditions: [{ conditionId: \"xxx\", value: 1000 }] },\n { name: \"Platinum\", conditions: [{ conditionId: \"xxx\", value: 2000 }] }\n ]\n5. tierset_get_tiers \u2192 Verify tiers created correctly\n\nGOTCHA - Valid tier condition attributes:\n- activeUnits (current spendable balance)\n- totalEarnedUnits (lifetime points - NOT 'earnedUnits'!)\n- totalSpending (lifetime spend amount)\n- monthsSinceJoiningProgram (tenure in months)\n\nCOMMON MISTAKE: Using 'earnedUnits' will fail - use 'totalEarnedUnits' for lifetime points.\n\n### 2. Register and Reward Member:\nmember_create \u2192 points_add (welcome bonus) \u2192 member_get (verify)\n\n### 3. Record Purchase and Earn Points:\ntransaction_create (with customerData) \u2192 triggers campaigns \u2192 points auto-added\n\n### 4. Redeem Reward:\nreward_list \u2192 reward_buy (deducts points) \u2192 reward_redeem (mark coupon used)\n\n### 5. Assign Unmatched Transaction:\ntransaction_list (matched=false) \u2192 transaction_assign_member \u2192 points earned\n\n### 6. Check Member Tier Progress:\nmember_get_tier_progress \u2192 shows current tier, next tier, progress %\n\n### 7. Double Points for VIP Members:\nsegment_create (with transaction_count criteria) \u2192 campaign_create (targeting that segment with give_points effect and pointsRule: \"transaction.grossValue * 2\")\n\n### 8. Purchase Achievement with Badge:\nbadge_list (find or note badgeTypeId) \u2192 achievement_create (type+trigger+aggregation+period required, badgeTypeId)\n\n### 9. Create Targeted Promotion:\nsegment_create (define audience) \u2192 campaign_create (type: earning, target: segment, segmentIds: [segmentId])\n\n### 10. Track Member Gamification:\nachievement_list_member_achievements \u2192 badge_get_member_badges \u2192 achievement_get_member_progress\n\n## Discovery Paths:\n- Members: member_list \u2192 member_get \u2192 points_get_balance\n- Tiers: tierset_list \u2192 tierset_get \u2192 tierset_get_tiers\n- Rewards: reward_category_list \u2192 reward_list \u2192 reward_get\n- Transactions: transaction_list \u2192 transaction_get\n- Campaigns: campaign_list \u2192 campaign_get \u2192 campaign_simulate\n- Segments: segment_list \u2192 segment_get \u2192 segment_get_members \u2192 campaign_create (audience targeting)\n- Achievements: achievement_list \u2192 badge_list \u2192 achievement_create (with badge)\n- Member Gamification: achievement_list_member_achievements \u2192 badge_get_member_badges\n\n## Important Patterns:\n- TIER SET = CONTAINER: One tier set holds ALL tiers (Bronze/Silver/Gold/etc)\n - Create ONE tier set, add ALL tiers in ONE tierset_update_tiers call\n - DO NOT create multiple tier sets for different tiers!\n- TIER SETUP: conditionId REQUIRED for tier thresholds\n 1. tierset_create returns conditions but you need the id from tierset_get\n 2. The conditionId is in tierset_get response: conditions[].id (this IS the conditionId)\n 3. Use the SAME conditionId for ALL tiers in tierset_update_tiers\n- TIER ATTRIBUTES: Use 'totalEarnedUnits' (NOT 'earnedUnits') for lifetime points\n- walletType uses CODE (e.g., 'default'), not UUID\n- Points operations: check balance with points_get_balance before spending\n- Reward flow: reward_buy returns couponCode, use reward_redeem to mark used\n- Transactions auto-match to members via customerData (email, phone, loyaltyCardNumber)\n\n### Pagination:\n- Traditional pagination: use page + perPage params\n- Cursor pagination: provide cursor from previous response for efficient deep pagination\n- Cursor-enabled tools: member_list, transaction_list, points_get_history\n- First scroll request: cursor=\"\" (empty string)\n- Subsequent requests: use cursor value from previous response\n- When cursor provided, page param is ignored\n\n### Campaign Patterns:\n- Campaign types: direct (standard), referral (referral programs)\n- Triggers: transaction (purchase), custom_event, time, achievement, etc.\n- Effects: give_points, give_reward, deduct_unit\n- Target campaigns to segments or tiers using audience.target: \"segment\" and audience.segments array\n- campaign_create requires activity.startsAt, rules[].name, and effects[].effect (use key effect, not type). pointsRule is a STRING expression.\n\n### Points Expression (pointsRule):\npointsRule is a STRING expression, not an object. Examples:\n- Fixed: \"100\"\n- Per dollar: \"transaction.grossValue * 10\"\n- Category-based: \"transaction.category('electronics').grossValue * 5\"\n- Capped: \"(transaction.grossValue * 2 >= 100) ? 100 : transaction.grossValue * 2\"\n- Rounded: \"round_up(0.1 * transaction.grossValue)\"\n\n### Segment Patterns:\n- Parts use OR logic (member matches ANY part)\n- Criteria within a part use AND logic (member must match ALL criteria in that part)\n- Common criteria: transaction_count (working; requires min+max). Other criteria may be rejected by the API.\n\n### Achievement Patterns:\n- Triggers: transaction, custom_event, points_transfer, reward_redemption, referral, achievement, tier_change, profile_update\n- periodGoal sets the target (e.g., 5 purchases, 1000 points spent)\n- period.type defines timeframe: day, week, month, year, last_day, calendarDays, calendarWeeks, calendarMonths, calendarYears\n- aggregation.type defines counting method: quantity (count events only)\n- Link to badge via badgeTypeId - badge auto-created if doesn't exist\n- uniqueAttribute for counting distinct values (e.g., unique products)\n\n### Achievement Period Types:\n- \"day\": Count all-time when consecutive=1\n- \"week\": Count per week (use consecutive=1 for all-time weekly tracking)\n- \"month\": Count per month\n- \"year\": Count per year\n- \"last_day\": Rolling window of N days (e.g., value: 7 for last 7 days)\n- \"calendarDays\": Reset daily at midnight\n- \"calendarWeeks\": Reset weekly (Monday)\n- \"calendarMonths\": Reset monthly (1st of month)\n- \"calendarYears\": Reset yearly (January 1st)\n\n### Streak Achievement Example (7-day login streak):\n{\n \"rules\": [{\n \"trigger\": \"custom_event\",\n \"event\": \"app_login\",\n \"type\": \"direct\",\n \"aggregation\": {\"type\": \"quantity\"},\n \"completeRule\": {\n \"periodGoal\": 1,\n \"period\": {\"type\": \"last_day\", \"value\": 7, \"consecutive\": 1}\n },\n \"limit\": {\n \"interval\": {\"type\": \"calendarDays\", \"value\": 1},\n \"value\": 1\n }\n }]\n}\n\n### Custom Event Workflows:\n\n#### Create Custom Event Campaign:\n1. custom_event_schema_list \u2192 Check if event type exists\n2. custom_event_schema_create (if needed) \u2192 Define event type and fields\n3. campaign_create with trigger: \"custom_event\", event: \"{event_type}\"\n4. Send events via custom_event_send to trigger campaign\n\n#### Create Achievement with Custom Event:\n1. custom_event_schema_create \u2192 Define event (e.g., \"location_visit\")\n2. achievement_create with:\n - rules[].trigger: \"custom_event\"\n - rules[].event: \"{event_type}\"\n - rules[].type: \"direct\"\n - rules[].aggregation.type: \"quantity\"\n - rules[].completeRule.periodGoal: {count}\n - rules[].completeRule.period: { type: \"day\", consecutive: 1 } (all-time)\n\n#### Create Campaign Triggered by Achievement:\n1. achievement_create \u2192 Get achievementId from response\n2. campaign_create with:\n - trigger: \"achievement\"\n - achievementId: \"{achievement_id}\"\n - rules with effects (give_points, give_reward)\n\n### Condition Operators (for campaigns/achievements):\n- is_equal, is_not_equal\n- gte, gt, lte, lt\n- is_between, is_not_between\n- is_time_between\n- is_day_of_week, is_day_of_month\n- contains, not_contains\n- starts_with, ends_with\n- one_of, not_one_of\n- expression (for custom logic)\n\n## Common Campaign Patterns (Industry-Agnostic)\n\nThese patterns work for any loyalty program - retail, QSR, aviation, fan engagement, hospitality, etc.\n\n### Pattern 1: Count Events Toward Goal (Achievements)\nUse case: Track member actions (visits, purchases, logins, check-ins) toward a milestone.\n1. custom_event_schema_create \u2192 Define your action type (e.g., \"store_visit\", \"app_login\")\n2. achievement_create with:\n - trigger: \"custom_event\"\n - event: \"{your_event_code}\"\n - aggregation: { type: \"quantity\" }\n - completeRule: { periodGoal: {count}, period: { type: \"day\", consecutive: 1 } }\n3. custom_event_send to log each action\n\n### Pattern 2: Points Per Action (Instant Rewards)\nUse case: Award instant points for each qualifying action.\n1. custom_event_schema_create (if not using transaction)\n2. campaign_create with:\n - trigger: \"custom_event\" (or \"transaction\")\n - event: \"{event_code}\" (for custom events)\n - rules: [{ effects: [{ effect: \"give_points\", pointsRule: \"50\" }] }]\n\n### Pattern 3: Conditional Rewards (Bonus Conditions)\nUse case: Award points only when a condition is met (e.g., early arrival, large purchase).\nUse ternary expressions in pointsRule instead of conditions array:\n- pointsRule: \"event.body.minutes_before >= 60 ? 25 : 0\"\n- pointsRule: \"transaction.grossValue >= 100 ? 50 : 0\"\n\n### Pattern 4: Execution Limits (Anti-Fraud)\nUse case: Limit rewards to prevent abuse (once per day, once per week).\nCampaign limits structure:\n{\n \"limits\": {\n \"executionsPerMember\": {\n \"value\": 1,\n \"interval\": { \"type\": \"calendarDays\", \"value\": 1 }\n }\n }\n}\nNote: Use \"calendarDays\" (not \"days\"), \"calendarWeeks\", \"calendarMonths\", \"calendarYears\".\nOmit interval entirely for lifetime/forever limit.\n\n### Pattern 5: Achievement-Triggered Bonus\nUse case: Award bonus points/rewards when member completes an achievement.\n1. achievement_create \u2192 Get achievementId from response\n2. campaign_create with:\n - trigger: \"achievement\"\n - achievementId: \"{achievement_id}\"\n - rules with give_points or give_reward effect\n\n### Pattern 6: Tiered Milestones\nUse case: Multiple achievement levels (bronze/silver/gold, 5/10/25 visits).\nCreate multiple achievements with increasing periodGoal values,\neach linked to a different bonus campaign with increasing rewards.\n\n### Pattern 7: Time-Limited Promotion\nUse case: Seasonal or promotional campaigns.\n{\n \"activity\": {\n \"startsAt\": \"2026-01-01 00:00+00:00\",\n \"endsAt\": \"2026-03-31 23:59+00:00\"\n }\n}\n\n### Pattern 8: Streak Tracking\nUse case: Reward consecutive daily/weekly actions (login streaks, workout streaks).\nAchievement with:\n- completeRule.period: { type: \"last_day\", value: 7 } (rolling 7-day window)\n- limit: { value: 1, interval: { type: \"calendarDays\", value: 1 } } (max 1 per day)\n\n## Phase 3: Analytics, Admin, Roles & Stores\n\n### Domain Model (Extended)\n\nAdmin (system user)\n \u2514\u2500\u2500 Roles (permission sets)\n \u2514\u2500\u2500 Permissions (resource + access level)\n \u2514\u2500\u2500 API Keys (programmatic access)\n\nStore (multi-tenant container)\n \u2514\u2500\u2500 Members, Campaigns, Rewards (isolated per store)\n\nAudit Log (compliance tracking)\n \u2514\u2500\u2500 eventType, entityType, entityId, username, timestamp\n\n### Analytics Workflows:\n\n#### 11. Get Program Overview:\nanalytics_dashboard \u2192 analytics_tiers \u2192 analytics_members\n\n#### 12. Analyze Points Economy:\nanalytics_points \u2192 analytics_units (per wallet) \u2192 points_get_histogram\n\n#### 13. Measure Campaign Performance:\nanalytics_campaigns \u2192 analytics_campaign_detail (specific campaign)\n\n#### 14. Track Transactions:\nanalytics_transactions \u2192 transaction_list (details)\n\n### Aggregation Queries (Top Spenders, Purchase Analysis):\n\nIMPORTANT: For queries like \"top 5 spenders in July 2025\", use this approach:\n\n#### 15. Find Top Spenders by Date Range:\n1. transaction_list(purchasedAtFrom, purchasedAtTo, perPage=50, cursor='') - returns customerId with each transaction\n2. Use cursor pagination to fetch ALL pages - even if there are 1000+ transactions\n3. Aggregate grossValue by customerId in your code\n4. Sort by total spent, take top N\n5. member_get for each top spender to get names/details\n\nCRITICAL - DO NOT TRY TO BE SMART OR OPTIMIZE:\n- ALWAYS iterate through ALL pages using cursor pagination - this is the ONLY correct approach\n- DO NOT skip pages or try to sample data - you will get inaccurate results\n- DO NOT use transaction_get individually - transaction_list already includes customerId\n- DO NOT try to find \"smarter\" analytics endpoints - they don't exist for per-customer aggregation\n- Large datasets (1000+ transactions) are normal - just keep paginating until cursor is empty\n- Start with cursor='' (empty string), use returned cursor for next request, repeat until done\n\nExample: Top 5 spenders July 2025\n- First request: transaction_list(purchasedAtFrom: \"2025-07-01\", purchasedAtTo: \"2025-07-31\", perPage: 50, cursor: \"\")\n- Each result includes: transactionId, grossValue, customerId, purchasedAt\n- Keep fetching with returned cursor until response has no cursor or empty transactions\n- Even if there are 1500 transactions (30 pages), iterate through ALL of them\n- Group by customerId, sum grossValue, sort descending, take 5\n\n### Admin & Security Workflows:\n\n#### 15. Create Admin with Limited Role:\nacl_get_resources \u2192 role_create \u2192 admin_create (with roleId)\n\n#### 16. Generate API Key for Integration:\nadmin_get \u2192 apikey_create \u2192 SAVE TOKEN IMMEDIATELY (shown once!)\n\n#### 17. Audit User Actions:\naudit_list (filter by username/entity) \u2192 audit_export (compliance report)\n\n### Store Multi-Tenancy:\n\n#### 18. Create New Store:\nstore_create \u2192 configure campaigns/tiers \u2192 member_create (with storeCode)\n\n### Analytics Query Patterns:\n- All analytics tools support dateFrom/dateTo ISO date filters\n- analytics_dashboard: high-level program metrics\n- analytics_units: wallet-specific metrics (requires walletTypeCode)\n- analytics_campaign_detail: detailed metrics for single campaign\n\n### Admin \u2192 Role \u2192 Permission Model:\n\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Admin \u2502\u2500\u2500\u2500\u2500\u25B6\u2502 Role \u2502\u2500\u2500\u2500\u2500\u25B6\u2502 Permission \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502 \u2502\n \u2502 resource + access\n \u2502 (VIEW, MODIFY, etc.)\n \u25BC\n\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 API Key \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\n### Store Multi-Tenancy:\n- Each store is isolated: members, campaigns, rewards, transactions\n- storeCode parameter routes requests to correct tenant\n- Default store used when storeCode omitted\n- IMPORTANT: Do NOT pass storeCode parameter unless the user explicitly asks to work with a different store. The configured default store should always be used unless the user requests otherwise.\n\n## Phase 4: Webhooks, Import & Export\n\n### Domain Model (Extended)\n\nWebhook Subscription (event notification)\n \u2514\u2500\u2500 eventName (event to subscribe to)\n \u2514\u2500\u2500 url (callback endpoint)\n \u2514\u2500\u2500 headers (custom HTTP headers)\n\nImport (bulk data upload)\n \u2514\u2500\u2500 type: member, groupValue, segmentMembers, unitTransferAdding, etc.\n \u2514\u2500\u2500 status: pending, processing, succeed, failed\n \u2514\u2500\u2500 items (individual row results)\n\nExport (bulk data download)\n \u2514\u2500\u2500 type: campaignCode, member, memberTier, memberSegment, rewardFulfillment\n \u2514\u2500\u2500 status: pending, done, failed, error\n \u2514\u2500\u2500 CSV file (when status='done')\n\n### Webhook Workflows:\n\n#### 19. Subscribe to Member Events for CRM Sync:\nwebhook_events \u2192 webhook_create (eventName: 'member.created', url: 'https://crm.example.com/webhook')\n\n#### 20. List and Manage Subscriptions:\nwebhook_list \u2192 webhook_get \u2192 webhook_update or webhook_delete\n\n### Import Workflows:\n\n#### 21. Bulk Import Members from CSV:\nimport_create (type: 'member', fileContent: CSV data) \u2192 import_list \u2192 import_get (check status)\n\n#### 22. Bulk Add Points to Members:\nimport_create (type: 'unitTransferAdding', fileContent: CSV) \u2192 poll import_get until complete\n\n### Export Workflows:\n\n#### 23. Export Campaign Codes:\nexport_create (type: 'campaignCode', filters: { campaignId }) \u2192 poll export_get (until status='done') \u2192 export_download\n\n#### 24. Export Member Data:\nexport_create (type: 'member') \u2192 export_get \u2192 export_download (returns CSV)\n\n### Webhook Patterns:\n- Use webhook_events to discover available event types before subscribing\n- API uses wrapper: { webhookSubscription: { eventName, url, headers? } }\n- Common events: member.created, member.updated, transaction.created, reward.purchased\n\n### Import Patterns:\n- Import is async: create returns importId, poll status with import_get\n- CSV format required - provide plain text, not base64\n- Types: member, groupValue, segmentMembers, unitTransferAdding, unitTransferSpending, transaction, campaignCode, rewardCoupon\n\n### Export Patterns:\n- Export is async: create returns exportId, poll status until 'done'\n- API body wrapper varies by type: { campaignCode: { filters... } }\n- Only call export_download when status='done'\n- Types: campaignCode, member, memberTier, memberSegment, rewardFulfillment\n";
|
package/dist/instructions.js
CHANGED
|
@@ -47,13 +47,24 @@ Badge (visual recognition)
|
|
|
47
47
|
|
|
48
48
|
## Key Workflows
|
|
49
49
|
|
|
50
|
-
### 1. Create
|
|
50
|
+
### 1. Create Multi-tier Loyalty Program:
|
|
51
|
+
CRITICAL CONCEPT: A tier set is a CONTAINER that holds ALL tiers (Bronze/Silver/Gold/etc).
|
|
52
|
+
- Create exactly ONE tier set
|
|
53
|
+
- Add ALL tiers to it in ONE call to tierset_update_tiers
|
|
54
|
+
- DO NOT create multiple tier sets for different tiers!
|
|
55
|
+
|
|
51
56
|
RECOMMENDED WORKFLOW:
|
|
52
57
|
1. wallet_type_list → Get wallet code (usually "default")
|
|
53
58
|
2. tierset_list → Check if tier set already exists
|
|
54
59
|
3. IF EXISTS: tierset_get → Get conditionId from existing tier set
|
|
55
|
-
ELSE: tierset_create → Create
|
|
56
|
-
4. tierset_update_tiers → Define tier thresholds
|
|
60
|
+
ELSE: tierset_create → Create ONE tier set, then tierset_get to get conditionId
|
|
61
|
+
4. tierset_update_tiers → Define ALL tier thresholds in ONE call:
|
|
62
|
+
tiers: [
|
|
63
|
+
{ name: "Bronze", conditions: [{ conditionId: "xxx", value: 0 }] },
|
|
64
|
+
{ name: "Silver", conditions: [{ conditionId: "xxx", value: 500 }] },
|
|
65
|
+
{ name: "Gold", conditions: [{ conditionId: "xxx", value: 1000 }] },
|
|
66
|
+
{ name: "Platinum", conditions: [{ conditionId: "xxx", value: 2000 }] }
|
|
67
|
+
]
|
|
57
68
|
5. tierset_get_tiers → Verify tiers created correctly
|
|
58
69
|
|
|
59
70
|
GOTCHA - Valid tier condition attributes:
|
|
@@ -61,7 +72,6 @@ GOTCHA - Valid tier condition attributes:
|
|
|
61
72
|
- totalEarnedUnits (lifetime points - NOT 'earnedUnits'!)
|
|
62
73
|
- totalSpending (lifetime spend amount)
|
|
63
74
|
- monthsSinceJoiningProgram (tenure in months)
|
|
64
|
-
- cumulatedEarnedUnits (similar to totalEarnedUnits)
|
|
65
75
|
|
|
66
76
|
COMMON MISTAKE: Using 'earnedUnits' will fail - use 'totalEarnedUnits' for lifetime points.
|
|
67
77
|
|
|
@@ -103,10 +113,13 @@ achievement_list_member_achievements → badge_get_member_badges → achievement
|
|
|
103
113
|
- Member Gamification: achievement_list_member_achievements → badge_get_member_badges
|
|
104
114
|
|
|
105
115
|
## Important Patterns:
|
|
116
|
+
- TIER SET = CONTAINER: One tier set holds ALL tiers (Bronze/Silver/Gold/etc)
|
|
117
|
+
- Create ONE tier set, add ALL tiers in ONE tierset_update_tiers call
|
|
118
|
+
- DO NOT create multiple tier sets for different tiers!
|
|
106
119
|
- TIER SETUP: conditionId REQUIRED for tier thresholds
|
|
107
120
|
1. tierset_create returns conditions but you need the id from tierset_get
|
|
108
121
|
2. The conditionId is in tierset_get response: conditions[].id (this IS the conditionId)
|
|
109
|
-
3. Use
|
|
122
|
+
3. Use the SAME conditionId for ALL tiers in tierset_update_tiers
|
|
110
123
|
- TIER ATTRIBUTES: Use 'totalEarnedUnits' (NOT 'earnedUnits') for lifetime points
|
|
111
124
|
- walletType uses CODE (e.g., 'default'), not UUID
|
|
112
125
|
- Points operations: check balance with points_get_balance before spending
|
|
@@ -46,16 +46,22 @@ export async function rewardList(input) {
|
|
|
46
46
|
}
|
|
47
47
|
export async function rewardCreate(input) {
|
|
48
48
|
const storeCode = getStoreCode(input.storeCode);
|
|
49
|
-
// API requires: translations (name only), reward (type), activity, visibility
|
|
50
|
-
// NOTE: usageLimit is NOT supported at creation time - API rejects it as "extra fields"
|
|
51
|
-
// Use reward_update after creation to set usageLimit
|
|
49
|
+
// API requires: translations (name only), reward (type), activity, visibility, usageLimit
|
|
52
50
|
// NOTE: description is NOT supported by the API at creation time
|
|
51
|
+
// CRITICAL: usageLimit shape depends on reward TYPE:
|
|
52
|
+
// - material/fortune_wheel: require BOTH perUser AND general
|
|
53
|
+
// - static_coupon/dynamic_coupon/conversion_coupon: require ONLY perUser (general is rejected as "extra fields")
|
|
54
|
+
const isCouponType = ["static_coupon", "dynamic_coupon", "conversion_coupon"].includes(input.reward);
|
|
55
|
+
const usageLimit = { perUser: input.usageLimitPerUser ?? 0 };
|
|
56
|
+
if (!isCouponType) {
|
|
57
|
+
usageLimit.general = input.usageLimitGeneral ?? 0;
|
|
58
|
+
}
|
|
53
59
|
const payload = omitUndefined({
|
|
54
60
|
translations: input.translations,
|
|
55
61
|
reward: input.reward,
|
|
56
62
|
activity: input.activity,
|
|
57
63
|
visibility: input.visibility,
|
|
58
|
-
|
|
64
|
+
usageLimit,
|
|
59
65
|
costInPoints: input.costInPoints,
|
|
60
66
|
usageInstruction: input.usageInstruction,
|
|
61
67
|
active: input.active,
|
|
@@ -76,6 +82,20 @@ export async function rewardCreate(input) {
|
|
|
76
82
|
};
|
|
77
83
|
}
|
|
78
84
|
catch (error) {
|
|
85
|
+
// Check for invalid reward type
|
|
86
|
+
const axiosError = error;
|
|
87
|
+
const apiErrors = axiosError.response?.data?.errors || [];
|
|
88
|
+
const rewardTypeError = apiErrors.find(e => e.path === "reward" && e.message.includes("selected choice is invalid"));
|
|
89
|
+
if (rewardTypeError) {
|
|
90
|
+
throw new OpenLoyaltyError({
|
|
91
|
+
code: "INVALID_REWARD_TYPE",
|
|
92
|
+
message: `Reward type '${input.reward}' is not valid`,
|
|
93
|
+
hint: "Valid reward types: 'material' (physical goods), 'static_coupon' (fixed discount code), " +
|
|
94
|
+
"'dynamic_coupon' (variable value coupon), 'conversion_coupon' (points-to-coupon conversion), " +
|
|
95
|
+
"'fortune_wheel' (gamified reward). NOTE: 'discount_code' is NOT a valid type - use 'static_coupon' instead.",
|
|
96
|
+
relatedTool: "ol_reward_create",
|
|
97
|
+
});
|
|
98
|
+
}
|
|
79
99
|
throw formatApiError(error, "ol_reward_create");
|
|
80
100
|
}
|
|
81
101
|
}
|
|
@@ -164,9 +184,35 @@ export async function rewardUpdate(input) {
|
|
|
164
184
|
payload.levels = input.levels;
|
|
165
185
|
if (input.segments !== undefined)
|
|
166
186
|
payload.segments = input.segments;
|
|
167
|
-
// usageLimit
|
|
187
|
+
// usageLimit handling depends on reward type:
|
|
188
|
+
// - material/fortune_wheel: require BOTH perUser AND general
|
|
189
|
+
// - coupon types: only accept perUser (general is rejected as "extra fields")
|
|
190
|
+
const existingRewardType = existing.reward;
|
|
191
|
+
const isCouponType = ["static_coupon", "dynamic_coupon", "conversion_coupon"].includes(existingRewardType || "");
|
|
192
|
+
const existingUsageLimit = existing.usageLimit;
|
|
168
193
|
if (input.usageLimitPerUser !== undefined) {
|
|
169
|
-
|
|
194
|
+
// User explicitly setting usageLimitPerUser
|
|
195
|
+
if (isCouponType) {
|
|
196
|
+
payload.usageLimit = { perUser: input.usageLimitPerUser };
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
payload.usageLimit = {
|
|
200
|
+
perUser: input.usageLimitPerUser,
|
|
201
|
+
general: existingUsageLimit?.general ?? 0,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
else if (existingUsageLimit) {
|
|
206
|
+
// Preserve existing usageLimit (API requires it on PUT for material type)
|
|
207
|
+
if (isCouponType) {
|
|
208
|
+
payload.usageLimit = { perUser: existingUsageLimit.perUser ?? 0 };
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
payload.usageLimit = {
|
|
212
|
+
perUser: existingUsageLimit.perUser ?? 0,
|
|
213
|
+
general: existingUsageLimit.general ?? 0,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
170
216
|
}
|
|
171
217
|
try {
|
|
172
218
|
await apiPut(`/${storeCode}/reward/${input.rewardId}`, { reward: payload });
|
|
@@ -68,6 +68,8 @@ export declare const rewardToolDefinitions: readonly [{
|
|
|
68
68
|
from: string;
|
|
69
69
|
to: string;
|
|
70
70
|
}>;
|
|
71
|
+
usageLimitPerUser: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
72
|
+
usageLimitGeneral: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
71
73
|
costInPoints: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
72
74
|
usageInstruction: import("zod").ZodOptional<import("zod").ZodString>;
|
|
73
75
|
active: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
|
@@ -20,15 +20,19 @@ export const rewardToolDefinitions = [
|
|
|
20
20
|
name: "ol_reward_create",
|
|
21
21
|
title: "Create New Reward",
|
|
22
22
|
description: "Create a new reward that members can redeem with points. " +
|
|
23
|
-
"
|
|
23
|
+
"REQUIRED FIELDS: " +
|
|
24
24
|
"1. translations: { en: { name: 'Reward Name' } }, " +
|
|
25
|
-
"2. reward: 'material' or 'static_coupon' or 'dynamic_coupon', " +
|
|
25
|
+
"2. reward: 'material' or 'static_coupon' or 'dynamic_coupon' or 'conversion_coupon' or 'fortune_wheel', " +
|
|
26
26
|
"3. activity: { from: '2026-01-01 00:00', to: '2027-12-31 23:59' }, " +
|
|
27
27
|
"4. visibility: { from: '2026-01-01 00:00', to: '2027-12-31 23:59' }. " +
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
28
|
+
"WARNING: 'discount_code' is NOT a valid type - use 'static_coupon' instead. " +
|
|
29
|
+
"USAGE LIMITS by type: " +
|
|
30
|
+
"- material/fortune_wheel: usageLimitPerUser + usageLimitGeneral (both sent, default 0 = unlimited). " +
|
|
31
|
+
"- static_coupon/dynamic_coupon/conversion_coupon: ONLY usageLimitPerUser (do NOT set usageLimitGeneral - API rejects it). " +
|
|
32
|
+
"TYPE-SPECIFIC requirements: " +
|
|
33
|
+
"- static_coupon: couponValue is REQUIRED (e.g., couponValue: 10). " +
|
|
34
|
+
"- conversion_coupon: unitsConversion.ratio and unitsConversion.rounding are REQUIRED. " +
|
|
35
|
+
"For tier targeting: set target='level' and levels=['tier-uuid-1', 'tier-uuid-2'].",
|
|
32
36
|
readOnly: false,
|
|
33
37
|
inputSchema: RewardCreateInputSchema,
|
|
34
38
|
handler: rewardCreate,
|
|
@@ -45,7 +49,9 @@ export const rewardToolDefinitions = [
|
|
|
45
49
|
name: "ol_reward_update",
|
|
46
50
|
title: "Update Reward",
|
|
47
51
|
description: "Update reward configuration. Cannot change reward type after creation. " +
|
|
48
|
-
"
|
|
52
|
+
"Uses GET-then-PUT pattern (fetches existing reward, merges changes, sends full object). " +
|
|
53
|
+
"usageLimitPerUser can be set here or at creation time. " +
|
|
54
|
+
"For material rewards, usageLimit.general is preserved from existing data on update.",
|
|
49
55
|
readOnly: false,
|
|
50
56
|
inputSchema: RewardUpdateInputSchema,
|
|
51
57
|
handler: rewardUpdate,
|
|
@@ -55,6 +55,8 @@ export declare const RewardCreateInputSchema: {
|
|
|
55
55
|
from: string;
|
|
56
56
|
to: string;
|
|
57
57
|
}>;
|
|
58
|
+
usageLimitPerUser: z.ZodOptional<z.ZodNumber>;
|
|
59
|
+
usageLimitGeneral: z.ZodOptional<z.ZodNumber>;
|
|
58
60
|
costInPoints: z.ZodOptional<z.ZodNumber>;
|
|
59
61
|
usageInstruction: z.ZodOptional<z.ZodString>;
|
|
60
62
|
active: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -26,16 +26,26 @@ const RewardVisibilityInputSchema = z.object({
|
|
|
26
26
|
from: z.string().describe("Visibility start datetime (format: 'YYYY-MM-DD HH:mm'). REQUIRED."),
|
|
27
27
|
to: z.string().describe("Visibility end datetime (format: 'YYYY-MM-DD HH:mm'). REQUIRED."),
|
|
28
28
|
});
|
|
29
|
-
// NOTE: usageLimit
|
|
30
|
-
//
|
|
29
|
+
// NOTE: usageLimit shape depends on reward TYPE:
|
|
30
|
+
// - material/fortune_wheel: require BOTH perUser AND general
|
|
31
|
+
// - coupon types (static_coupon, dynamic_coupon, conversion_coupon): only accept perUser
|
|
32
|
+
// Handler automatically sends the correct shape based on reward type.
|
|
31
33
|
export const RewardCreateInputSchema = {
|
|
32
34
|
storeCode: z.string().optional().describe("Store code for multi-tenant routing. DO NOT pass this parameter - the configured default will be used automatically. Only provide a value if the user explicitly asks to work with a different store."),
|
|
33
35
|
translations: RewardTranslationsInputSchema.describe("Reward name translations. At least 'en' key with { name } is REQUIRED."),
|
|
34
|
-
reward: z.enum(["static_coupon", "dynamic_coupon", "conversion_coupon", "material", "fortune_wheel"]).describe("Reward type
|
|
35
|
-
"
|
|
36
|
+
reward: z.enum(["static_coupon", "dynamic_coupon", "conversion_coupon", "material", "fortune_wheel"]).describe("Reward type. Valid values: " +
|
|
37
|
+
"'material' (physical goods like merchandise, experiences), " +
|
|
38
|
+
"'static_coupon' (fixed discount code - requires couponValue), " +
|
|
39
|
+
"'dynamic_coupon' (variable value coupon), " +
|
|
40
|
+
"'conversion_coupon' (converts points to coupon - requires unitsConversion), " +
|
|
41
|
+
"'fortune_wheel' (gamified reward). " +
|
|
42
|
+
"WARNING: 'discount_code' is NOT valid - use 'static_coupon' instead."),
|
|
36
43
|
activity: RewardActivityInputSchema.describe("Activity period when reward can be purchased. Use datetime format 'YYYY-MM-DD HH:mm'. REQUIRED."),
|
|
37
44
|
visibility: RewardVisibilityInputSchema.describe("Visibility period when reward is shown. Use datetime format 'YYYY-MM-DD HH:mm'. REQUIRED."),
|
|
38
|
-
|
|
45
|
+
usageLimitPerUser: z.number().optional().describe("Max redemptions per member (default: 0 = unlimited). Applies to ALL reward types."),
|
|
46
|
+
usageLimitGeneral: z.number().optional().describe("Max total redemptions across all members (default: 0 = unlimited). " +
|
|
47
|
+
"ONLY for 'material' and 'fortune_wheel' types. " +
|
|
48
|
+
"Do NOT set this for coupon types (static_coupon, dynamic_coupon, conversion_coupon) - API will reject it."),
|
|
39
49
|
costInPoints: z.number().optional().describe("Points required to redeem this reward."),
|
|
40
50
|
usageInstruction: z.string().optional().describe("Instructions for using the reward."),
|
|
41
51
|
active: z.boolean().optional().describe("Whether reward is active (default: false)."),
|
package/dist/tools/tierset.d.ts
CHANGED
|
@@ -218,7 +218,7 @@ export declare const tiersetToolDefinitions: readonly [{
|
|
|
218
218
|
}, {
|
|
219
219
|
readonly name: "ol_tierset_update_tiers";
|
|
220
220
|
readonly title: "Configure Tier Thresholds";
|
|
221
|
-
readonly description: "
|
|
221
|
+
readonly description: "Add ALL tiers to a tier set in ONE call. A tier set is a CONTAINER - add all tiers (Bronze, Silver, Gold, etc.) to it together.\n\n⚠️ CRITICAL: Pass ALL tiers in a SINGLE call to this endpoint. DO NOT call this multiple times with one tier each.\n\n⚠️ INPUT FORMAT: Pass 'name' and 'description' as TOP-LEVEL fields on each tier object.\n❌ WRONG: { translations: { en: { name: \"Bronze\" } } }\n✅ CORRECT: { name: \"Bronze\", description: \"Entry tier\" }\n\nExample - Creating 4 tiers in ONE call:\ntiers: [\n { name: \"Bronze\", description: \"Entry tier\", conditions: [{ conditionId: \"xxx\", value: 0 }] },\n { name: \"Silver\", description: \"500+ points\", conditions: [{ conditionId: \"xxx\", value: 500 }] },\n { name: \"Gold\", description: \"1000+ points\", conditions: [{ conditionId: \"xxx\", value: 1000 }] },\n { name: \"Platinum\", description: \"Top tier\", conditions: [{ conditionId: \"xxx\", value: 2000 }] }\n]\n\nThe conditionId must match the one from tierset_get response - use the SAME conditionId for all tiers.";
|
|
222
222
|
readonly readOnly: false;
|
|
223
223
|
readonly inputSchema: {
|
|
224
224
|
storeCode: z.ZodOptional<z.ZodString>;
|
package/dist/tools/tierset.js
CHANGED
|
@@ -42,14 +42,14 @@ export const TierSetUpdateTiersInputSchema = {
|
|
|
42
42
|
tierSetId: z.string().describe("The tier set ID to update tiers for."),
|
|
43
43
|
tiers: z.array(z.object({
|
|
44
44
|
levelId: z.string().optional().describe("Existing level ID (for updates). Omit for new tiers."),
|
|
45
|
-
name: z.string().describe("
|
|
46
|
-
description: z.string().optional().describe("
|
|
45
|
+
name: z.string().describe("Tier name as a TOP-LEVEL string (e.g., 'Bronze'). NOT in translations wrapper."),
|
|
46
|
+
description: z.string().optional().describe("Tier description as a TOP-LEVEL string. NOT in translations wrapper."),
|
|
47
47
|
active: z.boolean().optional().describe("Whether the tier is active. Defaults to true."),
|
|
48
48
|
conditions: z.array(z.object({
|
|
49
49
|
conditionId: z.string().describe("Condition ID from tierset_get response."),
|
|
50
50
|
value: z.number().describe("Threshold value for this condition (e.g., 400 points for Bronze)."),
|
|
51
51
|
})).describe("Array of condition thresholds. Each uses conditionId from tierset_get."),
|
|
52
|
-
})).describe("Array of tier definitions."),
|
|
52
|
+
})).describe("Array of tier definitions. Each tier needs 'name' as a direct string field, NOT wrapped in translations."),
|
|
53
53
|
};
|
|
54
54
|
export const TierSetGetTiersInputSchema = {
|
|
55
55
|
storeCode: z.string().optional().describe("Store code for multi-tenant routing. DO NOT pass this parameter - the configured default will be used automatically. Only provide a value if the user explicitly asks to work with a different store."),
|
|
@@ -90,14 +90,30 @@ export async function tiersetCreate(input) {
|
|
|
90
90
|
// REQUIRED fields:
|
|
91
91
|
// - translations.en.name (string)
|
|
92
92
|
// - conditions (array with attribute, optionally walletType)
|
|
93
|
+
//
|
|
94
|
+
// IMPORTANT: Only include description if actually provided - API rejects empty string as "extra field"
|
|
95
|
+
const enTranslation = {
|
|
96
|
+
name: input.name,
|
|
97
|
+
};
|
|
98
|
+
if (input.description) {
|
|
99
|
+
enTranslation.description = input.description;
|
|
100
|
+
}
|
|
101
|
+
// Explicitly map conditions to only include known fields (attribute + walletType)
|
|
102
|
+
// This strips any extra fields that might slip through Zod validation
|
|
103
|
+
const mappedConditions = input.conditions.map((c) => {
|
|
104
|
+
const condition = {
|
|
105
|
+
attribute: c.attribute,
|
|
106
|
+
};
|
|
107
|
+
if (c.walletType) {
|
|
108
|
+
condition.walletType = c.walletType;
|
|
109
|
+
}
|
|
110
|
+
return condition;
|
|
111
|
+
});
|
|
93
112
|
const tierSetPayload = {
|
|
94
113
|
translations: {
|
|
95
|
-
en:
|
|
96
|
-
name: input.name,
|
|
97
|
-
description: input.description || "",
|
|
98
|
-
},
|
|
114
|
+
en: enTranslation,
|
|
99
115
|
},
|
|
100
|
-
conditions:
|
|
116
|
+
conditions: mappedConditions,
|
|
101
117
|
};
|
|
102
118
|
const payload = { tierSet: tierSetPayload };
|
|
103
119
|
try {
|
|
@@ -220,8 +236,8 @@ export const tiersetToolDefinitions = [
|
|
|
220
236
|
{
|
|
221
237
|
name: "ol_tierset_list",
|
|
222
238
|
title: "List Loyalty Programs",
|
|
223
|
-
description: "List all tier sets.
|
|
224
|
-
"
|
|
239
|
+
description: "List all tier sets. A tier set is a CONTAINER that holds MULTIPLE tiers (e.g., Bronze/Silver/Gold). " +
|
|
240
|
+
"⚠️ ALWAYS check existing tier sets FIRST before creating - reuse if one exists. " +
|
|
225
241
|
"Returns tierSetId, name, active status, and tier count for each tier set.",
|
|
226
242
|
readOnly: true,
|
|
227
243
|
inputSchema: TierSetListInputSchema,
|
|
@@ -230,13 +246,13 @@ export const tiersetToolDefinitions = [
|
|
|
230
246
|
{
|
|
231
247
|
name: "ol_tierset_create",
|
|
232
248
|
title: "Create Loyalty Program",
|
|
233
|
-
description: "Create
|
|
234
|
-
"⚠️
|
|
235
|
-
"
|
|
249
|
+
description: "Create ONE tier set that will contain ALL your tiers (e.g., Bronze/Silver/Gold/Platinum). " +
|
|
250
|
+
"⚠️ CRITICAL: Create exactly ONE tier set, then add ALL tiers to it via tierset_update_tiers. " +
|
|
251
|
+
"DO NOT create multiple tier sets for different tiers - all tiers belong in ONE tier set! " +
|
|
252
|
+
"⚠️ LIMIT: Maximum 3 ACTIVE tier sets per store. ALWAYS call tierset_list FIRST to check existing tier sets. " +
|
|
253
|
+
"WORKFLOW: 1) tierset_list → 2) tierset_create (ONE tier set) → 3) tierset_get (get conditionId) → 4) tierset_update_tiers (add ALL tiers in one call). " +
|
|
236
254
|
"⚠️ API LIMITATION: Only 'name', 'description', and 'conditions' are accepted at creation time. " +
|
|
237
|
-
"
|
|
238
|
-
"Valid condition attributes: 'totalEarnedUnits' (lifetime points), 'activeUnits' (current balance), 'totalSpending', 'monthsSinceJoiningProgram'. " +
|
|
239
|
-
"COMMON MISTAKE: Use 'totalEarnedUnits' NOT 'earnedUnits' for lifetime points. " +
|
|
255
|
+
"Valid condition attributes: 'totalEarnedUnits' (lifetime points), 'activeUnits' (current balance), 'totalSpending'. " +
|
|
240
256
|
"For unit-based attributes, set walletType to 'default'.",
|
|
241
257
|
readOnly: false,
|
|
242
258
|
inputSchema: TierSetCreateInputSchema,
|
|
@@ -246,9 +262,8 @@ export const tiersetToolDefinitions = [
|
|
|
246
262
|
name: "ol_tierset_get",
|
|
247
263
|
title: "Get Loyalty Program Details",
|
|
248
264
|
description: "Get tier set details including conditionId values needed for tierset_update_tiers. " +
|
|
249
|
-
"CRITICAL: After tierset_create,
|
|
250
|
-
"
|
|
251
|
-
"Response includes conditions[].id (this is the conditionId) and conditions[].attribute.",
|
|
265
|
+
"CRITICAL: After tierset_create, call this to get the conditionId from conditions[].id. " +
|
|
266
|
+
"Use this SAME conditionId for ALL tiers when calling tierset_update_tiers.",
|
|
252
267
|
readOnly: true,
|
|
253
268
|
inputSchema: TierSetGetInputSchema,
|
|
254
269
|
handler: tiersetGet,
|
|
@@ -266,14 +281,23 @@ export const tiersetToolDefinitions = [
|
|
|
266
281
|
{
|
|
267
282
|
name: "ol_tierset_update_tiers",
|
|
268
283
|
title: "Configure Tier Thresholds",
|
|
269
|
-
description: `
|
|
284
|
+
description: `Add ALL tiers to a tier set in ONE call. A tier set is a CONTAINER - add all tiers (Bronze, Silver, Gold, etc.) to it together.
|
|
285
|
+
|
|
286
|
+
⚠️ CRITICAL: Pass ALL tiers in a SINGLE call to this endpoint. DO NOT call this multiple times with one tier each.
|
|
287
|
+
|
|
288
|
+
⚠️ INPUT FORMAT: Pass 'name' and 'description' as TOP-LEVEL fields on each tier object.
|
|
289
|
+
❌ WRONG: { translations: { en: { name: "Bronze" } } }
|
|
290
|
+
✅ CORRECT: { name: "Bronze", description: "Entry tier" }
|
|
270
291
|
|
|
271
|
-
Example
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
292
|
+
Example - Creating 4 tiers in ONE call:
|
|
293
|
+
tiers: [
|
|
294
|
+
{ name: "Bronze", description: "Entry tier", conditions: [{ conditionId: "xxx", value: 0 }] },
|
|
295
|
+
{ name: "Silver", description: "500+ points", conditions: [{ conditionId: "xxx", value: 500 }] },
|
|
296
|
+
{ name: "Gold", description: "1000+ points", conditions: [{ conditionId: "xxx", value: 1000 }] },
|
|
297
|
+
{ name: "Platinum", description: "Top tier", conditions: [{ conditionId: "xxx", value: 2000 }] }
|
|
298
|
+
]
|
|
275
299
|
|
|
276
|
-
The conditionId must match one from tierset_get response.`,
|
|
300
|
+
The conditionId must match the one from tierset_get response - use the SAME conditionId for all tiers.`,
|
|
277
301
|
readOnly: false,
|
|
278
302
|
inputSchema: TierSetUpdateTiersInputSchema,
|
|
279
303
|
handler: tiersetUpdateTiers,
|
|
@@ -10,14 +10,14 @@ const LabelInputSchema = z.object({
|
|
|
10
10
|
});
|
|
11
11
|
const TransactionHeaderInputSchema = z.object({
|
|
12
12
|
documentNumber: z.string().describe("Unique document number (required, unique per store)."),
|
|
13
|
-
purchasedAt: z.string().describe("Purchase date/time in ISO format (required)."),
|
|
13
|
+
purchasedAt: z.string().describe("Purchase date/time in ISO format (required). Use 'purchasedAt' NOT 'purchaseDate'. Example: '2026-01-15T10:00:00Z'."),
|
|
14
14
|
documentType: z.enum(["sell", "return"]).optional().describe("Document type: sell (default) or return."),
|
|
15
15
|
linkedDocumentNumber: z.string().optional().describe("Original document number (required for returns)."),
|
|
16
16
|
purchasePlace: z.string().optional().describe("Location/store where purchase was made."),
|
|
17
17
|
labels: z.array(LabelInputSchema).optional().describe("Custom key-value labels."),
|
|
18
18
|
});
|
|
19
19
|
const TransactionItemInputSchema = z.object({
|
|
20
|
-
sku: z.string().describe("Product SKU (required
|
|
20
|
+
sku: z.string().describe("Product SKU as a STRING (required). Use 'sku: \"PROD-123\"' NOT 'sku: { code: \"...\" }'."),
|
|
21
21
|
name: z.string().describe("Product name (required, max 255 chars)."),
|
|
22
22
|
grossValue: z.number().describe("Item gross value (required)."),
|
|
23
23
|
category: z.string().describe("Product category (required, max 255 chars)."),
|
|
@@ -219,6 +219,9 @@ export const transactionToolDefinitions = [
|
|
|
219
219
|
title: "Record Purchase",
|
|
220
220
|
description: "Record a purchase transaction. If customerData provided, auto-matches to member and triggers point campaigns. " +
|
|
221
221
|
"For returns, set documentType='return' and provide linkedDocumentNumber referencing original sale. " +
|
|
222
|
+
"⚠️ FIELD NAMES - use exactly these: " +
|
|
223
|
+
"header.purchasedAt (NOT purchaseDate), items[].sku as STRING (NOT object). " +
|
|
224
|
+
"Example: { header: { documentNumber: 'INV-001', purchasedAt: '2026-01-15T10:00:00Z' }, items: [{ sku: 'PROD-123', name: 'Product', grossValue: 50, category: 'Sales' }] }. " +
|
|
222
225
|
"Returns transactionId and pointsEarned if campaigns triggered.",
|
|
223
226
|
readOnly: false,
|
|
224
227
|
inputSchema: TransactionCreateInputSchema,
|
|
@@ -138,11 +138,18 @@ export async function walletTypeCreate(input) {
|
|
|
138
138
|
// Check for duplicate code error
|
|
139
139
|
const errorMsg = getErrorMessage(error);
|
|
140
140
|
if (errorMsg.toLowerCase().includes("code") && errorMsg.toLowerCase().includes("already")) {
|
|
141
|
+
const isDefaultCode = input.code === "default" || !input.code;
|
|
141
142
|
throw new OpenLoyaltyError({
|
|
142
143
|
code: "DUPLICATE_CODE",
|
|
143
|
-
message: `Wallet type with code '${input.code}' already exists`,
|
|
144
|
-
hint:
|
|
145
|
-
`
|
|
144
|
+
message: `Wallet type with code '${input.code || "default"}' already exists`,
|
|
145
|
+
hint: isDefaultCode
|
|
146
|
+
? `Every store comes with a 'Default wallet' (code: 'default') pre-installed. ` +
|
|
147
|
+
`You do NOT need to create it. Use ol_wallet_type_list() to find it, then ` +
|
|
148
|
+
`ol_wallet_type_update() to customize its name, units, or expiry settings. ` +
|
|
149
|
+
`Only create a new wallet if you need a SECOND point currency with a different code.`
|
|
150
|
+
: `A wallet type with code '${input.code}' already exists. ` +
|
|
151
|
+
`Use ol_wallet_type_list() to see all existing wallet types. ` +
|
|
152
|
+
`Either use a different code, or find the existing wallet and update it with ol_wallet_type_update().`,
|
|
146
153
|
relatedTool: "ol_wallet_type_create",
|
|
147
154
|
});
|
|
148
155
|
}
|
|
@@ -231,9 +238,9 @@ export const walletTypeToolDefinitions = [
|
|
|
231
238
|
name: "ol_wallet_type_list",
|
|
232
239
|
title: "List Point Currencies",
|
|
233
240
|
description: "List all available wallet types (point currencies). " +
|
|
234
|
-
"
|
|
235
|
-
"
|
|
236
|
-
"
|
|
241
|
+
"ALWAYS call this BEFORE creating a new wallet type - every store already has a 'Default wallet' " +
|
|
242
|
+
"(code: 'default', units: points) pre-installed. " +
|
|
243
|
+
"Returns walletTypeId (UUID), code (unique identifier like 'default'), and name for each wallet type.",
|
|
237
244
|
readOnly: true,
|
|
238
245
|
inputSchema: WalletTypeListInputSchema,
|
|
239
246
|
handler: walletTypeList,
|
|
@@ -252,21 +259,23 @@ export const walletTypeToolDefinitions = [
|
|
|
252
259
|
name: "ol_wallet_type_create",
|
|
253
260
|
title: "Create Point Currency",
|
|
254
261
|
description: "Create a new wallet type (point currency) for the loyalty program. " +
|
|
255
|
-
"
|
|
262
|
+
"IMPORTANT: Every store already has a 'Default wallet' (code: 'default', units: points) out of the box. " +
|
|
263
|
+
"ALWAYS call ol_wallet_type_list() FIRST to check what wallets exist before creating a new one. " +
|
|
264
|
+
"If the default wallet fits your needs, use it as-is or update it with ol_wallet_type_update(). " +
|
|
265
|
+
"Only create a new wallet if you need a SECOND currency (e.g., 'stars', 'coins', 'miles'). " +
|
|
266
|
+
"REQUIRED FIELDS (will fail without these): " +
|
|
256
267
|
"1. translations: { en: { name: 'Currency Name' } } - Name is REQUIRED. " +
|
|
257
268
|
"2. unitSingularName: 'point' (or 'coin', 'star', etc.) - The singular form. " +
|
|
258
269
|
"3. unitPluralName: 'points' (or 'coins', 'stars', etc.) - The plural form. " +
|
|
259
270
|
"4. unitDaysExpiryAfter: 'all_time_active' or number string like '365'. " +
|
|
260
|
-
"
|
|
261
|
-
"
|
|
262
|
-
"
|
|
263
|
-
"
|
|
264
|
-
"
|
|
265
|
-
"
|
|
266
|
-
"
|
|
267
|
-
"
|
|
268
|
-
"💡 EXAMPLE: { translations: { en: { name: 'Bonus Points' } }, unitSingularName: 'point', " +
|
|
269
|
-
"unitPluralName: 'points', unitDaysExpiryAfter: 'all_time_active', code: 'bonus' }",
|
|
271
|
+
"OPTIONAL: " +
|
|
272
|
+
"code: Unique identifier (auto-generated if omitted, cannot change later). " +
|
|
273
|
+
"allowNegativeBalance: true/false (default: false). " +
|
|
274
|
+
"unitExpiryDate: Annual expiry in 'MM-DD' format (e.g., '12-31'). " +
|
|
275
|
+
"allTimeNotLocked: TRUE = 'No pending' (points immediately available). " +
|
|
276
|
+
"unitDaysLocked: Only if allTimeNotLocked=false, set days for pending period. " +
|
|
277
|
+
"NOT SUPPORTED AT CREATION: 'active' and 'limits' - use ol_wallet_type_update after creation. " +
|
|
278
|
+
"NOTE: New wallets are 'blocked' for ~2 minutes after creation - wait before updating.",
|
|
270
279
|
readOnly: false,
|
|
271
280
|
inputSchema: WalletTypeCreateInputSchema,
|
|
272
281
|
handler: walletTypeCreate,
|