feat: Add basic schema for loyalty extension#251
feat: Add basic schema for loyalty extension#251ziwuzhou-google wants to merge 4 commits intoUniversal-Commerce-Protocol:mainfrom
Conversation
|
Great start on this extension!
The Problem: "loyalty": {
"memberships": [
{
"activated_tiers": ["T_1"],
"rewards": [
{ "redemption": { "amount": 100 } }
]
}
]
}The business server has no stable identifier to know which membership ID |
Both are correct. These identifiers should not have |
253aca7 to
d378fb7
Compare
sinhanurag
left a comment
There was a problem hiding this comment.
Good start based on all our discussions! Left some comments. Let's iterate on it more and discuss further.
docs/specification/loyalty.md
Outdated
| { | ||
| "id": "BEN_001", | ||
| "title": "Free shipping", | ||
| "description": "Complimentary standard shipping on all orders" |
There was a problem hiding this comment.
We will need some examples of cart, checkout, order objects (starting with checkout is fine) which have this extension as well as the discount object clearly displaying the benefit being applied and the discount object enumerating the benefit.
Like in this case "BEN_001" will apply to discounts.applied.allocations (path = fulfillment-options.total , amount = xx) etc.
Ref: https://ucp.dev/latest/specification/discount/#discounts-object
There was a problem hiding this comment.
Added an example here for shipping but I don't think the applies_on needs to go all the way to allocations level as the shipping benefit applies on the entire order.
|
|
||
| ```json | ||
| { | ||
| "memberships": [ |
There was a problem hiding this comment.
Can we also add a request/response example of how an instrument based discount will look like?
I am thinking of cases when there is a co branded instrument used for purchase. At that point in time the agent might or might not be aware of the associated discount. The agent will send the chosen FOP or identifier as a signal. This signal can be part of the loyalty extension in the incoming checkout/cart request or passed in a different placeholder. The response should contain a loyalty extension with the applied discount based on the instrument.
There was a problem hiding this comment.
I am actually fine reconciling it with the PR raised here - #250
-cc @igrigorik
So the ingress signal for loyalty identification can come from context.eligibility but then the response can contain the loyalty extension decorating the objects.
…and some documentation improvments.
There was a problem hiding this comment.
Great work on this extension @ziwuzhou-google 👏 the hierarchical memberships → tiers → benefits → rewards model is well-designed, and placing it in common/ for cross-vertical use is the right call.
We've been building a reference implementation of incentives + loyalty on UCP (MCP transport, Shopify + Voucherify API/MCP, ~22 ucp_* tools covering the full shopping lifecycle). Based on real agent-customer interactions, there are a few capabilities we've found essential that the current schema doesn't cover yet. Happy to contribute PRs for any of these if there's interest.
1. Earning Forecast - "What will I earn from this purchase?"
This is the single most impactful missing piece for agent-driven commerce. In our implementation, showing "You'll earn 120 Loyalty Stars/Points with this order, bringing your balance to 4,620" before the customer commits is one of the strongest purchase motivators. It also helps agents handle price objections - points earning becomes additional value on top of any discount.
The current schema has balance (what you have) and redemption (what you're spending), but no way to show what the customer gains from the transaction.
Proposed addition - a new earning_forecast field on membership_reward, response-only:
"earning_forecast": {
"type": "object",
"description": "Preview of rewards to be earned from the current transaction.",
"ucp_request": "omit",
"properties": {
"amount": {
"type": "integer",
"minimum": 0,
"description": "Total points to be earned if the transaction completes."
},
"rules": {
"type": "array",
"items": {
"type": "object",
"required": ["id", "amount"],
"properties": {
"id": { "type": "string", "description": "Earning rule identifier." },
"description": { "type": "string", "description": "Human-readable explanation (e.g. '2x points on footwear')." },
"amount": { "type": "integer", "minimum": 0, "description": "Points earned from this rule." }
}
},
"description": "Breakdown of earning rules contributing to the total."
},
"projected_balance": {
"type": "integer",
"minimum": 0,
"description": "Projected available balance after earning and any redemption from this transaction."
}
}
}
This fits naturally into the existing membership_reward structure - each reward currency gets its own earning forecast. The per-rule breakdown gives agents transparency to explain why the customer is earning (e.g. "2x points on footwear" vs "base earning on all purchases"), which makes loyalty feel tangible rather than a black box.
2. Tier Progression - "How close am I to the next tier?"
The current schema shows which tiers exist and which are activated, but doesn't tell the agent how close the customer is to qualifying for a higher tier. In practice this drives natural upsell: "You're only 200 points from Platinum - this purchase would get you there!"
Proposed addition to membership_tier:
"progression": {
"type": "object",
"description": "Customer's progress toward qualifying for this tier.",
"ucp_request": "omit",
"properties": {
"qualified": {
"type": "boolean",
"description": "Whether the customer currently qualifies for this tier."
},
"progress_amount": {
"type": "integer", "minimum": 0,
"description": "Current progress value toward qualification threshold."
},
"threshold_amount": {
"type": "integer", "minimum": 0,
"description": "Required value to qualify for this tier."
},
"remaining_amount": {
"type": "integer", "minimum": 0,
"description": "Gap between current progress and threshold."
}
}
}
This is also response-only. For tiers the customer already has, qualified: true. For higher tiers, the agent can use remaining_amount to suggest cart additions that would push the customer over the threshold.
3. Agent Journey Example - When to Present Loyalty Context
Regarding PR #250 (eligibility claims) - in our implementation the agent flow looks like:
- Identify - customer provides email or loyalty card → agent looks up member profile (memberships, tiers, rewards balance)
- Discover deals - agent checks what promotions + loyalty benefits apply to the current cart, including tier-specific benefits
- Present - agent shows price with discount + "you'll earn X points" + "you're Y away from next tier"
- Redeem (optional) - customer chooses to spend points → redemption.amount on the relevant reward, linked via applies_on to discount extension
- Complete - earning is confirmed in the response
A concrete request/response example in the spec doc showing this full flow - especially the interplay between loyalty benefits, discount applies_on references, beast deals, and earning in the response - would be very helpful for agent developers building on this extension.
Context
We have a working MCP-based UCP server implementing these patterns - earning forecast and the full incentives negotiation lifecycle (search best deal → validate → lock → redeem). Happy to share more details or contribute schema additions as a follow-up PR if any of these proposals are useful.
Summary
A new loyalty extension is introduced to facilitate high-fidelity loyalty experiences across various commerce journeys, ensuring that shoppers can for example sign-up for membership, access personalized benefits, redeem rewards, and manage memberships seamlessly across various existing and future new capabilities.
Motivation
Loyalty is a core concept in Commerce, serving as a primary driver for customer retention and long-term business growth. UCP in its current format can provide minimal and to some extent "hacky" support of loyalty in checkout for example. However, they are far from ideal and lack the generality. As Loyalty is a construct that spans across all verticals (retail, lodging, transportation, etc), the design is meant to encompass and capture the core common semantics of different capabilities and scenarios.
Design Details
Four core concepts are introduced in the schema and structured in a hierarchy:
With the help of these four building blocks, one can support use cases such as:
applies_onrefers to an applied discount in the discount extensionType of change
Please delete options that are not relevant.
functionality to not work as expected, including removal of schema files
or fields)
Checklist