-
Notifications
You must be signed in to change notification settings - Fork 431
feat: add Return Extension to UCP specification #257
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
3119aa9
1d11161
135ba78
28e20b0
e222826
87a7de0
e5990c8
5ee68ab
3746447
c53150e
5768e2e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| <!-- | ||
| Copyright 2026 UCP Authors | ||
|
|
||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at | ||
|
|
||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
|
|
||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| --> | ||
|
|
||
| # Return Extension | ||
|
|
||
| ## Overview | ||
|
|
||
| The Return Extension allows businesses to communicate the conditions, methods, timelines, and costs associated with returning physical items directly to the platform and the buyer, mirroring real-world commerce requirements. | ||
|
|
||
| By exposing the return policy natively in the UCP schema, AI agents and platforms can intelligently answer user queries like *"Can I return this in-store?"* or *"How many days do I have to return this?"* without forcing the user to leave the platform to hunt for a policy on the merchant's website. | ||
|
|
||
| This extension adds a `return_policies` field to Checkout containing: | ||
|
|
||
| * `return_policies[]` — conditions governed by the merchant for specific items. | ||
| * `window_type` — the category of return window (finite, lifetime, final sale, etc.) | ||
| * `return_days` — the number of days in the window. | ||
| * `methods[]` — permitted physical methods (in-store, by-mail, etc.) | ||
| * `fee` — the cost structure for that specific method. | ||
|
|
||
| **Mental model:** | ||
|
|
||
| * `return_policies[0]` Standard Apparel | ||
| * `line_item_ids` 👕👖 | ||
| * `window_type` = `finite_window` 🗓️ 30 Days | ||
| * `methods[0]` In-Store 🏬 | ||
| * `fee` = `free` ✅ | ||
| * `methods[1]` By Mail 📦 | ||
| * `fee` = `fixed_fee` $5.00 💸 | ||
| * `return_policies[1]` Final Sale | ||
| * `line_item_ids` ⌚ | ||
| * `window_type` = `final_sale` 🚫 | ||
| * `exchanges_allowed` = `false` | ||
|
|
||
| ## Schema | ||
|
|
||
| Return policies apply to physical items in a checkout session. Items not governed by a specific policy (e.g., digital services) may be omitted or covered by a default policy. | ||
|
|
||
| ### Properties | ||
|
|
||
| {{ extension_fields('return', 'return_policies') }} | ||
|
|
||
| ### Entities | ||
|
|
||
| #### Return Policy | ||
|
|
||
| {{ schema_fields('types/return_policy', 'return') }} | ||
|
|
||
| #### Return Method | ||
|
|
||
| {{ schema_fields('types/return_method', 'return') }} | ||
|
|
||
| #### Return Fee | ||
|
|
||
| {{ schema_fields('types/return_fee', 'return') }} | ||
|
|
||
| ## Rendering | ||
|
|
||
| Return policies are designed for proactive disclosures by the merchant. Platforms use these fields to provide transparency about the logistical requirements of a purchase before completion. | ||
|
|
||
| ### Human-Readable Fields | ||
|
|
||
| | Location | Field | Required | Purpose | | ||
| | ---------------------- | -------------- | -------- | --------------------------------------------------- | | ||
| | `return_policy` | `return_days` | No | Quantitative window for the return. | | ||
| | `return_method.fee` | `display_text` | No | Context for the fee (e.g., "Restocking Fee"). | | ||
| | `return_method.fee` | `amount` | No | Price in minor units for fixed fees. | | ||
|
|
||
| ### Business Responsibilities | ||
|
venkatesh-ucp marked this conversation as resolved.
venkatesh-ucp marked this conversation as resolved.
|
||
|
|
||
| **For `window_type`:** | ||
|
|
||
| * **MUST** accurately reflect the merchant's legal and commercial policy. | ||
| * **MUST** provide `return_days` if the type is `finite_window`. | ||
|
|
||
| **For `return_method.fee`:** | ||
|
|
||
| * **SHOULD** use `display_text` to explain the nature of the fee (e.g., "Prepaid Label", "Restocking Fee"). | ||
| * **MUST** provide `amount` if the type is `fixed_fee`. | ||
|
|
||
| ### Platform Responsibilities | ||
|
|
||
| Platforms **SHOULD** use return policies to answer buyer questions and provide assurance: | ||
|
|
||
| * Surface "Final Sale" warnings early in the checkout flow. | ||
| * Answer specific questions like "Is return shipping free?" by inspecting the `by_mail` method fee. | ||
| * Use `return_days` to calculate and display the specific return deadline based on the delivery date. | ||
|
|
||
| ## Examples | ||
|
|
||
| ### Mixed Cart | ||
|
|
||
| In this example, apparel items have a standard window, while a custom item is final sale. | ||
|
|
||
| ```json | ||
| { | ||
| "return_policies": [ | ||
| { | ||
| "id": "rp_apparel", | ||
| "line_item_ids": ["shirt", "pants"], | ||
| "window_type": "finite_window", | ||
| "return_days": 30, | ||
| "exchanges_allowed": true, | ||
| "methods": [ | ||
| { | ||
| "type": "in_store", | ||
| "fee": { | ||
| "type": "free", | ||
| "display_text": "Free In-Store Return" | ||
| } | ||
| }, | ||
| { | ||
| "type": "by_mail", | ||
| "fee": { | ||
| "type": "fixed_fee", | ||
| "amount": 500, | ||
| "display_text": "Return Shipping Fee" | ||
| } | ||
| } | ||
| ] | ||
| }, | ||
| { | ||
| "id": "rp_final_sale", | ||
| "line_item_ids": ["custom_engraved_watch"], | ||
| "window_type": "final_sale", | ||
| "exchanges_allowed": false | ||
| } | ||
| ] | ||
| } | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| { | ||
| "$schema": "https://json-schema.org/draft/2020-12/schema", | ||
| "$id": "https://ucp.dev/schemas/shopping/return.json", | ||
| "name": "dev.ucp.shopping.return", | ||
| "title": "Return Extension", | ||
| "description": "Extends Checkout with return policy support.", | ||
| "$defs": { | ||
| "return_policy": { | ||
|
venkatesh-ucp marked this conversation as resolved.
|
||
| "$ref": "types/return_policy.json" | ||
| }, | ||
| "return_method": { | ||
| "$ref": "types/return_method.json" | ||
| }, | ||
| "return_fee": { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To elaborate on @amithanda 's observation in 1.4, you can have this API be considerably more expressive for all parties if
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Per prior decisions, this fee is mostly part of the pre-purchase disclosure. It is mostly for display purposes and my goal has been to keep it as simple as possible and provide more flexibility to provide information to the merchant to make a decision. In this design, the platform is meant to mostly pass through this information to the user and use it for display purposes. All the complex behaviors around storing the data will be handled in the post-purchase PR. For what you are asking, merchants can achieve this today using the existing schema in two robust ways:
Example representation in existing schema: "methods": [
{
"type": "by_mail",
"fee": {
"type": "fixed_fee",
"amount": 1500,
"display_text": "15% Restocking Fee ($15.00 pre-computed)"
}
}
] |
||
| "$ref": "types/return_fee.json" | ||
| }, | ||
| "dev.ucp.shopping.checkout": { | ||
| "title": "Checkout with Return", | ||
| "description": "Checkout extended with return policies.", | ||
| "allOf": [ | ||
| { | ||
| "$ref": "checkout.json" | ||
| }, | ||
| { | ||
| "type": "object", | ||
| "properties": { | ||
| "return_policies": { | ||
| "type": "array", | ||
| "items": { | ||
| "$ref": "#/$defs/return_policy" | ||
| }, | ||
| "description": "Return policies applicable to items in the checkout.", | ||
| "ucp_request": "omit" | ||
| } | ||
| } | ||
| } | ||
| ] | ||
| }, | ||
| "dev.ucp.shopping.return": { | ||
| "platform_schema": { | ||
| "title": "Return Capability (Platform)", | ||
| "description": "Platform-level return capability configuration", | ||
| "allOf": [ | ||
| { | ||
| "$ref": "../capability.json#/$defs/platform_schema" | ||
| } | ||
| ] | ||
| }, | ||
| "business_schema": { | ||
| "title": "Return Capability (Business)", | ||
| "description": "Business-level return capability configuration", | ||
| "allOf": [ | ||
| { | ||
| "$ref": "../capability.json#/$defs/business_schema" | ||
| } | ||
| ] | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| { | ||
| "$schema": "https://json-schema.org/draft/2020-12/schema", | ||
| "$id": "https://ucp.dev/schemas/shopping/types/return_fee.json", | ||
| "title": "Return Fee", | ||
| "description": "The cost structure associated with a specific return method.", | ||
| "type": "object", | ||
| "required": ["type"], | ||
| "properties": { | ||
| "type": { | ||
| "type": "string", | ||
| "enum": ["free", "fixed_fee", "customer_responsibility"], | ||
|
venkatesh-ucp marked this conversation as resolved.
Outdated
|
||
| "description": "The cost structure for the return method." | ||
| }, | ||
| "amount": { | ||
| "$ref": "amount.json", | ||
| "description": "Fixed return fee charged by the merchant, represented in minor currency units (e.g., cents). Required if type is fixed_fee." | ||
| }, | ||
| "display_text": { | ||
| "type": "string", | ||
| "description": "Human-readable text to display against the fee to provide context to the buyer (e.g., 'Restocking Fee', 'Return Shipping Label')." | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| { | ||
| "$schema": "https://json-schema.org/draft/2020-12/schema", | ||
| "$id": "https://ucp.dev/schemas/shopping/types/return_method.json", | ||
| "title": "Return Method", | ||
| "description": "The physical method through which the buyer can return the item.", | ||
| "type": "object", | ||
| "required": ["type", "fee"], | ||
| "properties": { | ||
| "type": { | ||
| "type": "string", | ||
| "enum": ["in_store", "by_mail", "kiosk"], | ||
|
venkatesh-ucp marked this conversation as resolved.
Outdated
|
||
| "description": "The physical method through which the buyer can return the item." | ||
| }, | ||
| "fee": { | ||
| "$ref": "return_fee.json", | ||
| "description": "The cost structure associated with this specific return method." | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| { | ||
| "$schema": "https://json-schema.org/draft/2020-12/schema", | ||
| "$id": "https://ucp.dev/schemas/shopping/types/return_policy.json", | ||
| "title": "Return Policy", | ||
| "description": "Conditions, methods, timelines, and costs associated with returning physical items.", | ||
| "type": "object", | ||
| "required": ["id", "line_item_ids", "window_type"], | ||
| "properties": { | ||
| "id": { | ||
|
venkatesh-ucp marked this conversation as resolved.
Outdated
|
||
| "type": "string", | ||
| "description": "Unique identifier for the return policy." | ||
| }, | ||
| "line_item_ids": { | ||
| "type": "array", | ||
| "items": { | ||
| "type": "string" | ||
| }, | ||
| "description": "Line items governed by this return policy, allowing distinct policies per item." | ||
| }, | ||
| "window_type": { | ||
| "type": "string", | ||
| "enum": ["lifetime", "no_returns", "final_sale", "finite_window"], | ||
| "description": "The type of return window." | ||
| }, | ||
|
venkatesh-ucp marked this conversation as resolved.
Outdated
|
||
| "return_days": { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Consider also extending This handles non-retail domains naturally, a hotel cancellation (24h before check-in) has a different anchor than delivery entirely.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wanted some clarifications on how you expect this to work before I go ahead and make the changes. Option 1: As you mention, have the platform store the return policy or persist it in the Order and then calculate it whenever the fulfillment.events (delivered) is received given that majority return policies will kick in after the item is delivered (physical or digital). This has the complexity that the platform needs to calculate this number. Option 2: Add a field to the line_items part of the Order which the merchant updates whenever a fulfillment event (delivered) occurs. The platform the looks up the data to answer the "can I still return this?" question. Line items vs a global deadline for the Order since the return deadlines could be different based on the category of the product. for eg: electronics v/s clothing. Are we on the same page that option 2 is how this should be implemented?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Apologies for the missed message here, let me attempt to clarify the premise first. At checkout a business may communicate a policy. For example, "free returns for 30 days." The Platform is poorly positioned to translate this into an actual deadline which is the most relevant piece of information for the customer post-purchase. The platform is poorly positioned because in the existing schema, the only anchor provided to the platform is the time checkout occurred. But modern ecommerce is more complicated than that, for physical goods it may be (i) 30 days from purchase (ii) 30 days from fulfillment or (iii) 30 days from delivery. Amazon and Target use (iii), Sephora is (i). This brings us to the options you outlined. Option 1. We should avoid this as it forces the platform to calculate the returns deadline against the Policy which has insufficient fidelity. Providing the platform a referential hint that captured "duration from purchase/fulfillment/delivery" may be useful in any case but doesn't solve the undesirable outcome. In the case of fulfillment or delivery, it necessitates the platform track post-purchase notifications which is onerous. Option 2. Allows the business to serve as the source of truth which is directionally aligned to the protocol. The I'm aligned to Option II and it looks like the group has similarly weighed in — @sbeashwar called for extending Order with return_policies[] given the checkout TTL gap, and @amithanda's recommended spec prose already references the
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm good with Option II. The post order experience will be handled in a follow up PR. We should be filing the RFC for that next week'ish. |
||
| "type": "integer", | ||
| "description": "Number of days allowed for a return, typically starting from the date of delivery. Required if category is finite_window." | ||
|
venkatesh-ucp marked this conversation as resolved.
Outdated
|
||
| }, | ||
| "exchanges_allowed": { | ||
| "type": "boolean", | ||
| "description": "Indicates whether the buyer can exchange the item." | ||
| }, | ||
| "methods": { | ||
| "type": "array", | ||
| "items": { "$ref": "return_method.json" }, | ||
| "description": "Permitted physical methods for returning the item, along with their associated fee structures." | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.