POP Magic Row Technical Introduction
POP provides the backend integration layer for Magic Row recommendations. Magic Row aims to make the homepage feel intelligent, dynamic, and tailored to individual preferences, while laying the foundation for a scalable smart lobby that continuously adapts to user intent and behaviour.
For client integration, GFE calls POP to retrieve Magic Row recommendation decisions for a customer. POP validates and routes the request, retrieves recommendation data from CDS when needed, caches recommendation results in AWS ElastiCache Valkey, and returns a CloudEvents response to GFE.
This page introduces the POP architecture and documents the current /v1/decisions client integration contract. It intentionally does not cover running the services locally.
POP services
POP currently contains two services for the Magic Row recommendation flow.
| Service | Responsibility |
|---|---|
des-router |
POP entry endpoint. It accepts CloudEvents HTTP requests on /v1/decisions, validates the required headers and body, and routes the request to the downstream recommendation service by brand and event attributes. |
des-recommendations |
Recommendation orchestration service. It checks and refreshes Magic Row recommendation data in AWS ElastiCache Valkey, retrieves recommendations from CDS when required, and returns the decision response to des-router. |
High-level architecture
GFE integrates with POP as the recommendation decision API. POP integrates with CDS as the recommendation source and uses AWS ElastiCache Valkey as the recommendation cache.
---
config:
themeVariables:
fontSize: 24px
title: High-level architecture
---
flowchart LR
customer["Customer"]
gfe["GFE client"]
cds["CDS Magic Row recommendations"]
subgraph pop_service["POP service"]
direction TB
router["des-router"]
recommendations["des-recommendations"]
valkey["AWS ElastiCache Valkey recommendation cache"]
end
customer --> gfe
gfe -->|"CloudEvents request"| router
router -->|"Route by CloudEvent headers"| recommendations
recommendations -->|"Read / write cache"| valkey
recommendations -->|"Retrieve recommendations when needed"| cds
cds --> recommendations
recommendations --> router
router -->|"CloudEvents response"| gfe
Response path: CDS or Valkey returns data to des-recommendations, which returns the response to des-router, and des-router returns the CloudEvents response to GFE.
Magic Row recommendation scenarios
The response source depends on the customer state and whether questionnaire data exists.
| Scenario | Expected recommendation source |
|---|---|
| Customer completes the Magic Row questionnaire | Questionnaire-driven Magic Row recommendations from CDS. |
| New customer on day 0 does not complete the Magic Row questionnaire | DAY 0 recommendations. |
| Existing customer does not complete the Magic Row questionnaire | TAG recommendations, returned as "we think you like" recommendations. |
POP request flows
Request without refreshCache, cache miss
When refreshCache is omitted and no cached recommendation exists, des-recommendations retrieves Magic Row recommendations from CDS, saves the result in Valkey, and returns the response through des-router.
---
config:
themeVariables:
fontSize: 24px
title: POP request without refreshCache cache miss
---
sequenceDiagram
autonumber
participant GFE
participant Router as POP des-router
participant Recs as POP des-recommendations
participant Cache as AWS ElastiCache Valkey
participant CDS
GFE->>Router: POST /v1/decisions without refreshCache
Router->>Router: Validate CloudEvents headers and body
Router->>Recs: Route recommendation request
Recs->>Cache: Get cached recommendations by customer/product/brand
Cache-->>Recs: Cache miss
Recs->>CDS: Retrieve Magic Row recommendations
CDS-->>Recs: Recommendation result
Recs->>Cache: Save recommendation result
Recs-->>Router: Return recommendation response
Router-->>GFE: Return CloudEvents response
Request without refreshCache, cache hit
When refreshCache is omitted and cached recommendations are available, des-recommendations returns the cached result without calling CDS.
---
config:
themeVariables:
fontSize: 24px
title: POP request without refreshCache cache hit
---
sequenceDiagram
autonumber
participant GFE
participant Router as POP des-router
participant Recs as POP des-recommendations
participant Cache as AWS ElastiCache Valkey
GFE->>Router: POST /v1/decisions without refreshCache
Router->>Router: Validate CloudEvents headers and body
Router->>Recs: Route recommendation request
Recs->>Cache: Get cached recommendations by customer/product/brand
Cache-->>Recs: Cached recommendation result
Recs-->>Router: Return cached recommendation response
Router-->>GFE: Return CloudEvents response
Request with refreshCache: true
When refreshCache is true, des-recommendations bypasses the cache read path for the decision result, retrieves the latest Magic Row recommendations from CDS, refreshes the Valkey cache, and returns the refreshed response through des-router.
---
config:
themeVariables:
fontSize: 24px
title: POP request with refreshCache true
---
sequenceDiagram
autonumber
participant GFE
participant Router as POP des-router
participant Recs as POP des-recommendations
participant Cache as AWS ElastiCache Valkey
participant CDS
GFE->>Router: POST /v1/decisions with refreshCache true
Router->>Router: Validate CloudEvents headers and body
Router->>Recs: Route recommendation refresh request
Recs->>CDS: Retrieve latest Magic Row recommendations
CDS-->>Recs: Recommendation result
Recs->>Cache: Refresh cached recommendation result
Recs-->>Router: Return refreshed recommendation response
Router-->>GFE: Return CloudEvents response
Service access
Send requests from inside the platform network to the environment host for the environment you are integrating with.
| Environment | Host |
|---|---|
| qa | pop-service-qa.platformservices-dev.aws.private |
| nxt | pop-service-nxt.platformservices-dev.aws.private |
| drk | pop-service-drk.platformservices-prod.aws.private |
| prd | pop-service-prd.platformservices-prod.aws.private |
Endpoint:
Example target for nxt:
Request format
Requests are CloudEvents in HTTP binary content mode. The CloudEvent attributes are sent as HTTP headers and the event data is sent as a JSON request body.
The router currently routes gaming recommendation requests where:
ce-sourceisurn:uki:pop:gaming:mobile:iosce-typeisuki.pop.gaming.recommendation.requestce-subjectisgaming-magic-rowce-brandis one ofskybet,paddypower, orbetfair
Required headers
All of these headers must be present and non-empty.
| Header | Required value |
|---|---|
ce-source |
urn:uki:pop:gaming:mobile:ios |
ce-type |
uki.pop.gaming.recommendation.request |
ce-specversion |
1.0 |
ce-id |
Unique request/event id from the caller |
ce-subject |
gaming-magic-row |
ce-brand |
skybet, paddypower, or betfair |
ce-datacontenttype |
application/json |
Content-Type |
application/json |
ce-time is commonly sent by clients and is accepted by the router, but it is not part of the router's required-header list.
Request body
The JSON body contains the recommendation decision input.
| Field | Type | Required | Description |
|---|---|---|---|
accountId |
string | Yes | Customer account id. |
product |
string | Yes | Product to request recommendations for. The tested value is vegas. |
refreshCache |
boolean | No | When true, asks the downstream recommendation service to refresh cached data. |
Example body:
Happy path example
curl --location -i \
--header 'ce-id: req-qa-200' \
--header 'ce-source: urn:uki:pop:gaming:mobile:ios' \
--header 'ce-type: uki.pop.gaming.recommendation.request' \
--header 'ce-brand: skybet' \
--header 'ce-subject: gaming-magic-row' \
--header 'ce-specversion: 1.0' \
--header 'ce-datacontenttype: application/json' \
--header 'Content-Type: application/json' \
--data '{
"accountId": "999",
"product": "vegas"
}' \
'http://pop-service-nxt.platformservices-dev.aws.private/v1/decisions'
Successful responses are also CloudEvents in HTTP binary content mode. The response body contains the decision result.
Example response headers:
HTTP/1.1 200 OK
ce-brand: skybet
ce-datacontenttype: application/json
ce-id: req-qa-200
ce-source: urn:uki:pop:recommendations
ce-specversion: 1.0
ce-subject: gaming-magic-row
ce-type: gaming.recommendation.response
content-type: application/json
ce-time: 2026-05-14T14:44:24.639411Z
Example response body:
{
"accountId": "999",
"source": "DAY 0",
"recommendations": [
{
"rank": 1,
"score": 123.0,
"gamecode": "the-goonies-hyg-vbp"
},
{
"rank": 2,
"score": 124.0,
"gamecode": "king-kong-cash-jk-vbp"
},
{
"rank": 3,
"score": 125.0,
"gamecode": "jungle-jackpots-vbp"
},
{
"rank": 4,
"score": 126.0,
"gamecode": "congo-cash-vpr"
}
],
"errors": []
}
Response contract
For a successful decision response, clients should expect:
| Field | Type | Description |
|---|---|---|
accountId |
string | Account id from the request. |
source |
string | Recommendation source, for example TAG or DAY 0. |
recommendations |
array | Ranked recommendation list. |
recommendations[].rank |
number | Rank assigned by the recommendation service. |
recommendations[].score |
number | Score assigned by the recommendation service. |
recommendations[].gamecode |
string | Recommended game code. |
errors |
array | Empty on a successful response. |
Non-200 responses
Non-200 responses are returned when request validation fails, no route matches the CloudEvent attributes, the media type is unsupported, or the downstream service times out.
Observed statuses:
| Status | Typical cause | Client expectation |
|---|---|---|
400 Bad Request |
Missing or empty required header, unsupported ce-brand, or invalid JSON/body field. |
Fix the request. The response usually contains an errors attribute with validation details. |
404 Not Found |
No route matched the request attributes, for example unsupported ce-type or ce-subject. |
Check route-driving CloudEvent headers. The response contains an errors attribute. |
415 Unsupported Media Type |
Content-Type is not application/json. |
Send Content-Type: application/json. The current observed response body may be empty. |
504 Gateway Timeout |
Downstream recommendation service did not respond before the router timeout. | Retry according to the caller's retry policy. The response contains an errors attribute. |
Router-generated error responses use these CloudEvent headers:
| Header | Value |
|---|---|
ce-type |
uki.pop.router.error |
ce-source |
urn:uki:pop:router |
ce-specversion |
1.0 when the router needs to generate it |
ce-id |
Caller ce-id when available, otherwise a generated id |
ce-time |
Response timestamp |
Example missing header response:
HTTP/1.1 400 Bad Request
ce-specversion: 1.0
ce-subject: gaming-magic-row
ce-brand: skybet
ce-datacontenttype: application/json
Content-Type: application/json
ce-time: 2026-05-14T14:44:33.398560Z
ce-type: uki.pop.router.error
ce-source: urn:uki:pop:router
ce-id: 92cb17ff-6cfc-4370-ba9c-c528c76b51bb
Example body validation response:
{
"accountId": "",
"source": null,
"recommendations": [],
"errors": [
{
"code": "VALIDATION_ERROR",
"message": "Missing required creator property 'accountId' (index 0)"
}
]
}
Example timeout response: