RFD: Adding Play Context to Consumer Context Service
RFD: Adding Play Context to Consumer Context Service
Section titled “RFD: Adding Play Context to Consumer Context Service”Author: Steve Hollinger Date: 2026-04-06 Status: Draft
Summary
Section titled “Summary”This document proposes adding a Play domain to the Consumer Context Service (CCS), giving Rewards Assistant and other consumer-facing AI agents access to a user’s Fetch Play activity, progress, and actionable opportunities. Play is Fetch’s rewards-by-gaming feature powered by Adjoe, where users earn points by installing/playing external games and completing milestones, plus a daily word-guessing mini-game.
Motivation
Section titled “Motivation”CCS currently aggregates context from 12+ upstream services across Products, Offers, and Users domains. Play is a major engagement surface with rich per-user data (game installs, milestone progress, streaks, promotions) that is not yet accessible to our AI agents. Adding Play context enables Rewards Assistant to:
- Inform users about their Play progress and available opportunities
- Surface high-value actions (“you’re 1 milestone away from completing Coin Master”)
- Alert users to expiring rewards, streak risks, and active promotions
- Provide a unified view of the user’s full Fetch experience
Play Ecosystem Overview
Section titled “Play Ecosystem Overview”What is Fetch Play?
Section titled “What is Fetch Play?”Users earn points two ways:
- Adjoe Milestones — Install external games, hit sequential/bonus milestones (e.g. “Reach Level 50”), earn points per milestone
- Daily Guess Mini-Game — Daily word guessing game with streaks, leaderboards, and point rewards
Source Services
Section titled “Source Services”| Service | Owner | Purpose | Key Data |
|---|---|---|---|
| gameplay-service | RAPPS collective | Core Play service | Lifetime points, game installs, milestone progress, promotions |
| mini-game-service | RAPPS collective | Daily Guess game | Streak, score, attempts histogram, leaderboard ranking |
| play-support-service | RAPPS collective | Support tooling | User overview, per-app history, support award tracking |
Supporting Services (not directly integrated)
Section titled “Supporting Services (not directly integrated)”| Service | Purpose |
|---|---|
| rewarded-apps-bff | Mobile BFF aggregator (proto/Connect RPC — not suitable for CCS) |
| quest-service | Quests/challenges (“install 3 games this week”) |
| app-service | App metadata (icons, titles, descriptions) |
| play-promo-service | Promotion management |
| adjoe-s2s-service | Adjoe backend communication (trending shelf) |
| fetch-play-user-profile | Iterable profile sync |
Upstream API Contracts
Section titled “Upstream API Contracts”gameplay-service
Section titled “gameplay-service”Base URLs:
| Environment | URL |
|---|---|
| Stage | https://stage-gameplay-service.us-east-1.stage-services.fetchrewards.com |
| Preprod | https://preprod-gameplay-service.us-east-1.preprod-services.fetchrewards.com |
| Prod | https://prod-gameplay-service.us-east-1.prod-services.fetchrewards.com |
Auth: userId header (Fetch user UUID)
GET /user
Section titled “GET /user”User’s lifetime Play summary.
{ "totalPoints": "5000", "numberOfGamesInstalled": 10, "lastTransactionDate": "2025-02-15T14:30:00Z", "lastInstallDate": "2025-02-10T10:00:00Z", "isNewUser": false}Note: totalPoints is a string, not integer.
GET /milestones/{appId}
Section titled “GET /milestones/{appId}”Per-app milestone progress. This is the richest source of in-game progress data.
{ "milestones": [ { "appId": "com.moonactive.coinmaster", "installId": "e958f4d8-7f63-4832-b343-0599bf5ed9c1", "platform": "android", "userId": "60a578602b9aa31fe73b7506", "milestoneId": "91428c9f-a62e-425d-9320-a34d25a82992", "description": {"en": "Level 1", "es": "Nivel 1"}, "isBonus": false, "pointAmount": 98, "status": "COMPLETED", "installDate": 1736381630, "pending": false, "milestoneType": "SEQUENTIAL", "availableDate": 1736384957, "completedDate": 1736384957, "expirationDate": null, "playDuration": 82, "index": 1, "pointAwardedAmount": 98, "timedCoins": 150, "timedCoinsExpiration": 1736471357 } ]}Key fields:
status:COMPLETED,AVAILABLE,UNAVAILABLE,EXPIREDmilestoneType:SEQUENTIAL,BONUS,TIME_BOOSTED,IAP_SPEND,IAP_COUNTdescription: Localized map — the human-readable “what to do” (e.g. “Complete Level 50”)index: Sequential position in the milestone chain- Timestamps are Unix seconds (not ISO)
GET /recentActivity?pageSize=N&pageNumber=N
Section titled “GET /recentActivity?pageSize=N&pageNumber=N”Paginated list of recently completed milestones. Same milestone schema as above, filtered to status: COMPLETED.
GET /nextUp?pageSize=N&pageNumber=N&platform=X
Section titled “GET /nextUp?pageSize=N&pageNumber=N&platform=X”Available milestones to complete across all installed games. Same milestone schema, filtered to status: AVAILABLE.
GET /completedApps?pageSize=N&pageNumber=N
Section titled “GET /completedApps?pageSize=N&pageNumber=N”{ "apps": [ { "appId": "com.moonactive.coinmaster", "installId": "a261f1c1-506b-4aa3-ba3e-5cf2169f7973", "platform": "android", "userId": "60a578602b9aa31fe73b7506", "totalPoints": 5924, "status": "COMPLETED", "installDate": 1736381630, "completedDate": 1741637294, "allMilestonesCompleted": true } ]}App status: COMPLETED, ACTIVE, EXPIRED, UNKNOWN
GET /expiredApps?pageSize=N&pageNumber=N
Section titled “GET /expiredApps?pageSize=N&pageNumber=N”Same schema as completedApps, with status: EXPIRED.
GET /currentPromotion
Section titled “GET /currentPromotion”Returns 200 with promotion or 204 No Content.
{ "startDate": 1740000000, "endDate": 1742000000, "multiplier": 2.5, "promotionType": "FETCH_PLAY_PROMO", "version": 1}mini-game-service
Section titled “mini-game-service”Base URLs:
| Environment | URL |
|---|---|
| Stage | https://stage-mini-game-service.us-east-1.stage-services.fetchrewards.com |
| Prod | https://prod-mini-game-service.us-east-1.prod-services.fetchrewards.com |
Auth: userId in URL path
GET /api/v1/user/{userId}/game-stats?gameId=daily-guess
Section titled “GET /api/v1/user/{userId}/game-stats?gameId=daily-guess”{ "userId": "67ed4e9b89ab15e9a432dc60", "gameId": "daily-guess", "clock": { "serverNowUtc": "2026-03-03T21:48:20Z", "gameTimezone": "America/Chicago", "todayGameDay": "2026-03-03T00:00:00-06:00", "nextGameDay": "2026-03-04T00:00:00-06:00" }, "activity": { "lastPlayedAtUtc": "2026-03-03T13:46:51Z", "lastWinAtUtc": "2026-03-03T13:46:51Z", "lastLossAtUtc": "2026-01-25T18:54:27Z" }, "stats": { "currentStreakDays": 31, "maxStreakDays": 113, "bestScore": 1, "customStats": { "attemptsHistogram": { "0": 5, "1": 2, "2": 12, "3": 57, "4": 48, "5": 43, "6": 21 } } }, "ui": { "suggestedEntryPoint": "CompletedSuccessfully", "startedToday": true }, "rewardAmount": 0}Key fields:
currentStreakDays/maxStreakDays— streak trackingattemptsHistogram— distribution of guesses across all gamessuggestedEntryPoint:StartStreak,ContinueStreak,CompletedSuccessfully,CompletedUnsuccessfullystartedToday— whether they’ve played today- All timestamps are RFC3339 UTC
- Game day boundaries use America/Chicago timezone
GET /api/v1/user/{userId}/game-results?gameId=daily-guess¢ralDate=2026-03-03
Section titled “GET /api/v1/user/{userId}/game-results?gameId=daily-guess¢ralDate=2026-03-03”Per-day result card.
{ "outcome": "Win", "currentStreakDays": 31, "performanceSummary": { "args": [ {"type": "Int", "intValue": 5}, {"type": "Int", "intValue": 6}, {"type": "TimeDuration", "intValue": 132} ], "primaryMetricArgs": [ {"type": "Int", "intValue": 5}, {"type": "Int", "intValue": 6} ], "secondaryMetricArgs": [ {"type": "TimeDuration", "intValue": 132} ] }, "ranking": { "percentileRank": 58.91, "betterThanUserIds": ["68d457923b09ee447ec5f85a"] }, "statChips": [ {"key": "games_played", "value": {"type": "Int", "intValue": 188}}, {"key": "longest_streak", "value": {"type": "Int", "intValue": 113}}, {"key": "win_rate", "value": {"type": "Float", "floatValue": 97.34}} ], "rewardAmount": 0, "isOptimistic": false}GET /api/v1/user/{userId}/completed-games
Section titled “GET /api/v1/user/{userId}/completed-games”[ { "eventId": "E#daily-guess#2026-03-03#GAME_COMPLETED", "gameId": "daily-guess", "receivedAt": "2026-03-03T15:12:10Z", "rewardAmount": 10 }]play-support-service
Section titled “play-support-service”Base URLs:
| Environment | URL |
|---|---|
| Stage | https://stage-play-support-service.fetchrewards.com |
| Prod | https://prod-play-support-service.fetchrewards.com |
Auth: None required for GET endpoints.
Note: Uses legacy stage-{name}.fetchrewards.com URL pattern (no region).
GET /play-support-service/overview/{userId}
Section titled “GET /play-support-service/overview/{userId}”{ "userId": "60a578602b9aa31fe73b7506", "platform": "ios", "totalPointsEarned": 5350, "totalRetentionPointsEarned": 50, "totalMissingMilestonePointsEarned": 4000, "missingMilestoneSupportAwardedCount": 2, "totalMissingGamePointsEarned": 1000, "missingGameSupportAwardedCount": 1, "totalManuallyCompletedPoints": 5000, "manuallyCompletedCount": 3, "apps": [ { "appId": "com.moonactive.coinmaster", "appName": "Monopoly Go", "platform": "ios", "userInstalled": true, "userAppStatus": "In Progress" } ]}App status values: Unconfirmed Install, Installed, In Progress, Complete, Closed - Support Awarded
GET /play-support-service/app-history/{userId}
Section titled “GET /play-support-service/app-history/{userId}”[ { "userId": "60a578602b9aa31fe73b7506", "appId": "com.moonactive.coinmaster", "appName": "Monopoly Go", "platform": "ios", "userAppStatus": "In Progress", "lastActivityTimeInSeconds": 1705942562, "lastMilestoneActivityTimeInSeconds": 1705942562, "totalPointsEarned": 150, "inAppMilestonesCompletedCount": 2, "totalMissingMilestonePointsEarned": 0, "missingMilestoneSupportAwardedCount": 0, "totalMissingGamePointsEarned": 0, "missingGameSupportAwardedCount": 0, "installDateTimeInSeconds": 1705942558 }]GET /play-support-service/app-details/{userId}/{appId}
Section titled “GET /play-support-service/app-details/{userId}/{appId}”{ "userId": "60a578602b9aa31fe73b7506", "appId": "com.moonactive.coinmaster", "appName": "Monopoly Go", "platform": "ios", "userAppStatus": "In Progress", "installTimeInSeconds": 0, "totalPointsAwarded": 100, "numberMilestonesCompleted": 1, "activityRecords": [ { "userId": "60a578602b9aa31fe73b7506", "appId": "com.moonactive.coinmaster", "appName": "Monopoly Go", "platform": "ios", "numberPointsAwarded": 100, "pointAwardedReason": "", "awardType": "in_app", "eventTimestampInSeconds": 1605942569, "manuallyCompleted": false, "numberMilestonesAwarded": 1 } ], "missingMilestones": null}Adjoe Inbound Webhooks (Reference)
Section titled “Adjoe Inbound Webhooks (Reference)”These are what Adjoe sends TO gameplay-service. CCS does not consume these directly, but they’re useful context for understanding data provenance.
Transaction Webhook (GET /webhook)
Section titled “Transaction Webhook (GET /webhook)”All query params:
?user_uuid=534ef82ee4b0644f1c5e3b3c&sid=bf6e0d3c&coin_amount=37&trans_uuid=b6574ccc-7213-4900-8e48-252bc98b6baf&app_name=Block%20Blast&app_id=com.block.juggle&milestone_name=level_4000_score&milestone_description=Reach%204000%20Score&event_type=sequential&reward_type=Playtime&install_uuid=dad2cf67-c420-487a-80e0-4b87947772c5&event_boost_factor=1.5&timed_coins_amount=50&timed_coins_event_duration=60&promotion_tag=summer_2024Rich Install (POST /v2/install)
Section titled “Rich Install (POST /v2/install)”{ "UserID": "534ef82ee4b0644f1c5e3b3c", "AppID": "6449094229", "AppName": "Match Factory!", "Platform": "ios", "InstallUUID": "dad2cf67-c420-487a-80e0-4b87947772c5", "AttributedAt": "2024-11-10T02:27:44.861Z", "OfferExpiresAt": "2024-12-10T02:27:44.861Z", "RewardType": "Advance+", "TotalRewardAmount": 28586, "RewardMilestones": [ { "Name": "level_7_completed", "Description": "Complete Level 7", "Amount": 131, "PlayDuration": 5, "TaskType": "SEQUENTIAL" }, { "Name": "purchase", "Description": "Make Your First Purchase!", "Amount": 1088, "TaskType": "BONUS" } ], "CashbackConfig": { "exchangeRate": 0.5, "maxLimitPerCampaignCoins": 50000, "maxLimitPerCampaignUSD": 100 }, "EventBoostFactor": 1.5, "AppCategory": "gaming", "Placement": "feed_cta"}Play Event Surface — Kafka & SNS Topics
Section titled “Play Event Surface — Kafka & SNS Topics”Beyond the REST APIs above, Play produces rich event streams relevant for real-time personalization and graph-based intelligence.
SNS Topic: {env}-gameplay-service-events
Section titled “SNS Topic: {env}-gameplay-service-events”The primary event bus for Play, with two event types filtered by SNS subscriptions:
POINTS_AWARDED — emitted when a user completes a game milestone:
| Field | Type | Notes |
|---|---|---|
| UserId | string | Required |
| AppName | string | Game name |
| AppId | string | Adjoe app identifier |
| AppCategory | string | Game category |
| Platform | string | iOS / Android |
| Points | int | Points earned |
| MilestoneName | string | e.g. “Reach Level 10” |
| MilestoneNumber | *int | Ordinal position |
| RewardLevel | *string | Reward tier |
| RewardType | *string | Reward classification |
| IsBonusMilestone | *bool | Whether bonus milestone |
| IsFirstTransaction | bool | First-ever Play transaction |
| EventBoostFactor | *float32 | Boost multiplier |
| Timestamp | time.Time | Event time |
APP_INSTALL — emitted when a user installs a game via Adjoe webhook:
| Field | Type | Notes |
|---|---|---|
| UserId | string | |
| AppId | string | |
| AppName | string | |
| Platform | string | iOS / Android |
| AppCategory | string | Game category |
| CPI | interface{} | Cost per install |
| Timestamp | time.Time |
CDC Topic: gameplay-service-gameplay-db-cdc
Section titled “CDC Topic: gameplay-service-gameplay-db-cdc”DynamoDB change data capture streaming gameplay user state via Debezium. Used by the Sequin ML pipeline.
Mobile Events (WebSocket Service → Snowflake)
Section titled “Mobile Events (WebSocket Service → Snowflake)”Table: FETCH_SERVICES_PROD.WEBSOCKET_SERVICE.WS_FETCH_PLAY_STAGE
Mini-game lifecycle events:
mini_game_section_impression— entry point visiblemini_game_section_tapped— entry point card tappedfetch_play_mini_game_started— game starts after webview loadsfetch_play_mini_game_completed— game finishedfetch_play_mini_game_results_impression— results screen displayedfetch_play_mini_game_results_share_tapped— share button tappedmini_game_share_completed— share action successful
Funnel events:
fetch_play_landing_viewed— user navigates to Play tabfetch_play_get_started_tapped— user taps Get Startedfetch_play_play_now_tapped— user taps Play Nowfetch_play_receipt_cta_viewed— user views receipt detail CTAfetch_play_receipt_cta_tapped— user taps receipt detail CTA
SNS Subscribers
Section titled “SNS Subscribers”| Queue | Filters | Purpose |
|---|---|---|
| gameplay-push-notification-queue | POINTS_AWARDED | Push notifications for points |
| battlepass-service-play-events-queue | POINTS_AWARDED | Battle pass progression |
| recommendations-play-engagement | — | Recs orchestrator input |
| High-point-amounts queue | POINTS_AWARDED | High-value event tracking |
| Referral completion queue | POINTS_AWARDED | Referral program triggers |
Current Play Data Consumers
Section titled “Current Play Data Consumers”| Consumer | Data Ingested | Status |
|---|---|---|
| recommendations-orchestrator | Play engagement via SQS | Merged (March 2026) |
| recommendations-input-listener | PlayEngagementKafkaListener → SNS → Faber | Live |
| Faber (recommendations-proxy-service) | Flat feature vectors for personalization | Live |
| Sequin (3rd-party ML) | CDC pipeline via Debezium → Iceberg | In progress (RF-1132) |
| battlepass-service | POINTS_AWARDED events | Live |
Snowflake Data Assets
Section titled “Snowflake Data Assets”| Table | Purpose |
|---|---|
| GAMEPLAY_TRANSACTION_STAGE | Points earned with milestone data |
| GAMEPLAY_INSTALL_STAGE | Game install data from Adjoe |
| GAMEPLAY_USER_STAGE | Player lifetime earning points |
| GAMEPLAY_MILESTONES_STAGE | Flattened milestone information |
| WS_FETCH_PLAY_STAGE | Mobile engagement events |
Integration Path (TBD)
Section titled “Integration Path (TBD)”There are two possible approaches for CCS:
-
REST polling (simpler) — Call gameplay-service/mini-game-service/play-support-service REST APIs on demand. Simpler to implement, matches current CCS pattern, but data is only as fresh as the request.
-
Event-driven (richer) — Subscribe to
{env}-gameplay-service-eventsSNS topic and maintain local state. Enables real-time awareness of Play activity (e.g. “you just earned 98 points!”), but adds complexity (SQS consumer, local storage, state management). The consumer-graph-worker already follows this pattern for Neo4j.
Direction TBD — depends on whether Rewards Assistant needs real-time Play awareness or if request-time freshness is sufficient.
Proposed CCS Integration
Section titled “Proposed CCS Integration”Architecture Decision: Direct Service Calls (Not BFF)
Section titled “Architecture Decision: Direct Service Calls (Not BFF)”The rewarded-apps-bff aggregates Play data for mobile clients using proto/Connect RPC with complex dependency graphs. For CCS, we should call downstream services directly because:
- BFF is designed for mobile UI rendering, not AI context
- BFF adds unnecessary aggregation overhead and an extra network hop
- Direct calls match our existing pattern (all other CCS clients are direct)
- We need raw data to compose into agent-friendly context, not pre-rendered UI sections
New Domain: internal/play/
Section titled “New Domain: internal/play/”Following the existing pattern (products/, offers/, users/):
internal/ play/ types.go # PlayContext, GameProgress, MiniGameStats, PlayOpportunity service.go # GetPlayContext(userId) orchestrating parallel calls clients/ gameplay.go # gameplay-service client minigame.go # mini-game-service client playsupport.go # play-support-service clientProposed PlayContext Type
Section titled “Proposed PlayContext Type”type PlayContext struct { User PlayUser // Lifetime summary (from gameplay-service) Overview *PlayOverview // Aggregated overview (from play-support-service) AppHistory []AppHistory // Per-app history with status (from play-support-service) Games []GameProgress // Per-game milestone progress (from gameplay-service) MiniGame *MiniGameStats // Daily Guess stats (nil if never played) Promotion *ActivePromotion // Current promo (nil if none) Opportunities []PlayOpportunity // Ranked actionable opportunities}Client Adapter Plan
Section titled “Client Adapter Plan”| Client | Service | Endpoints Used | Value |
|---|---|---|---|
GameplayClient | gameplay-service | GET /user, GET /nextUp, GET /completedApps, GET /expiredApps, GET /milestones/{appId}, GET /currentPromotion | Real-time milestone progress, available actions, promotions |
MiniGameClient | mini-game-service | GET /api/v1/user/{userId}/game-stats, GET /api/v1/user/{userId}/completed-games | Streak tracking, daily guess stats |
PlaySupportClient | play-support-service | GET /overview/{userId}, GET /app-history/{userId}, GET /app-details/{userId}/{appId} | Pre-aggregated summaries, app names, support history |
play-support-service: Why It’s In Scope
Section titled “play-support-service: Why It’s In Scope”play-support-service is an event-sourced materialized view of gameplay-service data. It consumes POINTS_AWARDED and APP_INSTALL events from gameplay-service via SNS/SQS and stores them in DynamoDB (partitioned by userId, sorted by event date). All reads query DynamoDB and aggregate in-memory.
While it overlaps with gameplay-service, it provides three things gameplay-service does not:
-
Pre-aggregated per-app summaries —
GET /app-history/{userId}returns total points, milestone counts, install dates, and activity timestamps per app in a single call. Without this, we’d need to paginate through gameplay-service milestones and aggregate ourselves. -
App names — gameplay-service returns only
appId(e.g.com.moonactive.coinmaster). play-support-service stores and returnsappName(e.g. “Coin Master”) because it captures the name from gameplay events. This avoids needing a separateapp-serviceintegration. -
Support award history — Tracks manual point awards with zendesk ticket IDs, award reasons (“OTE Missing Milestone”, “OTE Missing Game”), and who awarded them. Useful for Rewards Assistant to understand if a user has had support interactions around Play.
play-support-service Architecture
Section titled “play-support-service Architecture”Data ingestion: gameplay-service → SNS (gameplay-service-events) → SQS (play-support-in-app-event-queue) → 4 SQS worker goroutines → facade.ProcessEvent() → DynamoDB
DynamoDB schema: Table: {env}-play-support-events PK: userId SK: {eventDateUnixSeconds}|{transactionId} (newest first)
Each record: appId, appName, platform, pointsAwarded, awardType (in-app|support), milestoneName, milestoneDescription, supportAwardedReason, zendeskTicketId, manuallyCompleted, rewardType, eventBoostFactor
Read path: All GET endpoints → db.GetAll(userId) → in-memory aggregation → responseApp Status State Machine
Section titled “App Status State Machine”play-support-service derives app status from event history:
Unconfirmed Install → Installed (install event received) → In Progress (in-app points earned) → Closed - Support Awarded (manually completed) → Complete (all milestones done)Complete and Closed - Support Awarded are final states.
How CCS Uses play-support-service
Section titled “How CCS Uses play-support-service”| CCS Use Case | play-support-service Endpoint | Why Not gameplay-service |
|---|---|---|
| ”What games has this user played?” | GET /app-history/{userId} | Returns app names + aggregated stats in one call |
| ”User overview for Play” | GET /overview/{userId} | Pre-computed totals across all apps |
| ”What happened with this specific game?” | GET /app-details/{userId}/{appId} | Full activity log with milestone descriptions |
| ”Has this user had support issues?” | GET /overview/{userId} | Support award counts, manual completion tracking |
play-support-service Data Quirks
Section titled “play-support-service Data Quirks”- No auth on GETs — all read endpoints are unauthenticated
- Legacy URL pattern —
stage-play-support-service.fetchrewards.com(no region subdomain) - Timestamps are Unix seconds —
lastActivityTimeInSeconds,installDateTimeInSeconds,eventTimestampInSeconds - Platform derived from appId — contains ”.” = Android, all numeric = iOS, else unknown
- Event-sourced — data is eventually consistent with gameplay-service (SNS/SQS lag)
Opportunity Detection
Section titled “Opportunity Detection”The richest value-add for Rewards Assistant is deriving actionable opportunities from raw Play data. Each opportunity has a type, description, point value, and urgency signal.
Opportunity Types
Section titled “Opportunity Types”| Type | Source Data | Signal | Example |
|---|---|---|---|
| Available Milestone | GET /nextUp | status: AVAILABLE, pointAmount | ”Complete Level 50 in Match Factory for 240 points” |
| Expiring Timed Coins | milestones with timedCoins > 0 | timedCoinsExpiration approaching | ”150 bonus coins on Coin Master expire in 6 hours” |
| Active Promotion | GET /currentPromotion | multiplier > 1 | ”2.5x points boost running through Jan 15” |
| Almost-Complete Game | completedApps + milestones | Few milestones remaining vs total | ”5,200 of 5,924 points earned in Coin Master — 2 milestones left” |
| Expiring Game | expiredApps | expirationDate approaching | ”Match Factory rewards expire in 4 days — 1,500 points unclaimed” |
| Streak at Risk | game-stats | startedToday: false && currentStreakDays > 0 | ”31-day Daily Guess streak — don’t forget to play today” |
| Daily Guess Available | game-stats | suggestedEntryPoint: StartStreak/ContinueStreak | ”Today’s Daily Guess is ready” |
| Cashback Offer | milestones with cashbackConfig | milestoneType: IAP_SPEND/IAP_COUNT | ”Make a purchase in Match Factory for up to $10 back in points” |
| Repeatable Milestone | milestones | repetitiveRemainingCount > 0 | ”Earn points 3 more times from in-app purchases” |
Ranking Signals
Section titled “Ranking Signals”Opportunities can be scored using:
| Signal | Source | Weight Rationale |
|---|---|---|
pointAmount × multiplier | milestone + promotion | Raw reward value |
expirationDate proximity | milestones/apps | Urgency — value lost if not acted on |
completedMilestones / totalMilestones | per-app data | Completion proximity — high sunk cost |
playDuration | milestones | Investment already made |
currentStreakDays | game-stats | Streak loss risk — high emotional value |
timedCoinsExpiration proximity | milestones | Time-sensitive bonus |
Data Quirks & Implementation Notes
Section titled “Data Quirks & Implementation Notes”| Aspect | Detail |
|---|---|
| Timestamp inconsistency | gameplay-service uses Unix seconds for milestones but ISO strings for /user. mini-game-service uses RFC3339 everywhere. play-support-service uses Unix seconds. |
| totalPoints is a string | gameplay-service /user returns totalPoints as string, not int. Must parse. |
| Game day timezone | mini-game-service uses America/Chicago for day boundaries, streaks, and event IDs. |
| 204 for no promotion | GET /currentPromotion returns 204 No Content (not an empty object) when no promo is active. |
| Pagination | recentActivity, nextUp, completedApps, expiredApps are all paginated (0-indexed pageNumber). |
| No auth on play-support | GET endpoints on play-support-service require no authentication. |
| URL patterns differ | gameplay-service and mini-game-service use stage-{name}.us-east-1.stage-services.fetchrewards.com. play-support-service uses legacy stage-{name}.fetchrewards.com. |
Open Questions
Section titled “Open Questions”-
quest-service integration — Should we include quest/challenge progress? This is a separate system (Bitbucket repo, different team) that tracks Fetch-defined challenges like “install 3 games this week”. Adds complexity but rounds out the Play picture.
-
App metadata enrichment — gameplay-service returns
appIdbut not app names or icons. Should we integrateapp-servicefor display metadata, or is the appId + milestone descriptions sufficient for agent context? -
Caching strategy — User Play data changes with game activity. What TTL is appropriate? Suggestion: 5 minutes for user/milestone data, 15 minutes for promotion, no cache for mini-game stats (streak accuracy matters).
-
Platform parameter —
GET /nextUpaccepts aplatformparam (ios/android). Do we know the user’s platform from other CCS context, or should we fetch both? -
Pagination limits — How many games/milestones should we fetch? Power users may have 10+ games. Suggestion: cap at
pageSize=50for nextUp/recentActivity, fetch all completedApps/expiredApps.
References
Section titled “References”- gameplay-service repo — Go 1.24, OpenAPI spec at
api/openapi.yaml - gameplay-service FETCH_PLAY.md — Full Play overview
- gameplay-service API.md — API reference
- mini-game-service repo — Daily Guess service
- mini-game-service ARCHITECTURE.md
- play-support-service repo
- rewarded-apps-bff repo — Mobile BFF (reference only)
- RAPPS Onboarding — Confluence onboarding doc
- Slack:
#collective-rapps-dev,#fetch-play,#rewarded-apps-alarms