From 3119aa918031bbe6d01a342cc393528ef1d7300a Mon Sep 17 00:00:00 2001 From: Venkatesh Hodavdekar Date: Wed, 11 Mar 2026 17:04:06 +0000 Subject: [PATCH 01/11] feat: add Return Extension to UCP specification This change introduces the Return Extension, allowing businesses to communicate return conditions, methods, timelines, and costs. Key changes: - Added JSON schemas for Return Policy, Method, and Fee. - Extended Checkout and Order with return policies. - Renamed Return Policy 'category' to 'return_window_type'. - Added specification documentation and updated site navigation. Closes #TBD --- docs/specification/return.md | 142 ++++++++++++++++++ mkdocs.yml | 1 + source/schemas/shopping/return.json | 60 ++++++++ source/schemas/shopping/types/return_fee.json | 23 +++ .../schemas/shopping/types/return_method.json | 19 +++ .../schemas/shopping/types/return_policy.json | 37 +++++ 6 files changed, 282 insertions(+) create mode 100644 docs/specification/return.md create mode 100644 source/schemas/shopping/return.json create mode 100644 source/schemas/shopping/types/return_fee.json create mode 100644 source/schemas/shopping/types/return_method.json create mode 100644 source/schemas/shopping/types/return_policy.json diff --git a/docs/specification/return.md b/docs/specification/return.md new file mode 100644 index 000000000..0f2821338 --- /dev/null +++ b/docs/specification/return.md @@ -0,0 +1,142 @@ + + +# 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. + * `return_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` 👕👖 + * `return_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` ⌚ + * `return_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 + +**For `return_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"], + "return_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"], + "return_window_type": "final_sale", + "exchanges_allowed": false + } + ] +} +``` diff --git a/mkdocs.yml b/mkdocs.yml index 563de73a4..47185d6c6 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -47,6 +47,7 @@ nav: - Buyer Consent Extension: specification/buyer-consent.md - Discounts Extension: specification/discount.md - Fulfillment Extension: specification/fulfillment.md + - Return Extension: specification/return.md - Cart Capability: - Overview: specification/cart.md - HTTP/REST Binding: specification/cart-rest.md diff --git a/source/schemas/shopping/return.json b/source/schemas/shopping/return.json new file mode 100644 index 000000000..10295a03c --- /dev/null +++ b/source/schemas/shopping/return.json @@ -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": { + "$ref": "types/return_policy.json" + }, + "return_method": { + "$ref": "types/return_method.json" + }, + "return_fee": { + "$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" + } + ] + } + } + } +} diff --git a/source/schemas/shopping/types/return_fee.json b/source/schemas/shopping/types/return_fee.json new file mode 100644 index 000000000..d958012ea --- /dev/null +++ b/source/schemas/shopping/types/return_fee.json @@ -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"], + "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')." + } + } +} diff --git a/source/schemas/shopping/types/return_method.json b/source/schemas/shopping/types/return_method.json new file mode 100644 index 000000000..8ed1434a2 --- /dev/null +++ b/source/schemas/shopping/types/return_method.json @@ -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"], + "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." + } + } +} diff --git a/source/schemas/shopping/types/return_policy.json b/source/schemas/shopping/types/return_policy.json new file mode 100644 index 000000000..9c4ff5a22 --- /dev/null +++ b/source/schemas/shopping/types/return_policy.json @@ -0,0 +1,37 @@ +{ + "$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", "return_window_type"], + "properties": { + "id": { + "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." + }, + "return_window_type": { + "type": "string", + "enum": ["lifetime", "no_returns", "final_sale", "finite_window"], + "description": "The type of return window." + }, + "return_days": { + "type": "integer", + "description": "Number of days allowed for a return, typically starting from the date of delivery. Required if category is finite_window." + }, + "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." + } + } +} From 1d11161951a742a129b3e100feb1c4e493e1e17c Mon Sep 17 00:00:00 2001 From: Venkatesh Hodavdekar Date: Tue, 31 Mar 2026 17:20:20 +0000 Subject: [PATCH 02/11] refactor: rename return_window_type to window_type in Return Policy --- docs/specification/return.md | 12 ++++++------ source/schemas/shopping/types/return_policy.json | 8 +++++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/specification/return.md b/docs/specification/return.md index 0f2821338..51883ce4f 100644 --- a/docs/specification/return.md +++ b/docs/specification/return.md @@ -25,7 +25,7 @@ By exposing the return policy natively in the UCP schema, AI agents and platform This extension adds a `return_policies` field to Checkout containing: * `return_policies[]` — conditions governed by the merchant for specific items. - * `return_window_type` — the category of return window (finite, lifetime, final sale, etc.) + * `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. @@ -34,14 +34,14 @@ This extension adds a `return_policies` field to Checkout containing: * `return_policies[0]` Standard Apparel * `line_item_ids` 👕👖 - * `return_window_type` = `finite_window` 🗓️ 30 Days + * `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` ⌚ - * `return_window_type` = `final_sale` 🚫 + * `window_type` = `final_sale` 🚫 * `exchanges_allowed` = `false` ## Schema @@ -80,7 +80,7 @@ Return policies are designed for proactive disclosures by the merchant. Platform ### Business Responsibilities -**For `return_window_type`:** +**For `window_type`:** * **MUST** accurately reflect the merchant's legal and commercial policy. * **MUST** provide `return_days` if the type is `finite_window`. @@ -110,7 +110,7 @@ In this example, apparel items have a standard window, while a custom item is fi { "id": "rp_apparel", "line_item_ids": ["shirt", "pants"], - "return_window_type": "finite_window", + "window_type": "finite_window", "return_days": 30, "exchanges_allowed": true, "methods": [ @@ -134,7 +134,7 @@ In this example, apparel items have a standard window, while a custom item is fi { "id": "rp_final_sale", "line_item_ids": ["custom_engraved_watch"], - "return_window_type": "final_sale", + "window_type": "final_sale", "exchanges_allowed": false } ] diff --git a/source/schemas/shopping/types/return_policy.json b/source/schemas/shopping/types/return_policy.json index 9c4ff5a22..fc2e24bdc 100644 --- a/source/schemas/shopping/types/return_policy.json +++ b/source/schemas/shopping/types/return_policy.json @@ -4,7 +4,7 @@ "title": "Return Policy", "description": "Conditions, methods, timelines, and costs associated with returning physical items.", "type": "object", - "required": ["id", "line_item_ids", "return_window_type"], + "required": ["id", "line_item_ids", "window_type"], "properties": { "id": { "type": "string", @@ -12,10 +12,12 @@ }, "line_item_ids": { "type": "array", - "items": { "type": "string" }, + "items": { + "type": "string" + }, "description": "Line items governed by this return policy, allowing distinct policies per item." }, - "return_window_type": { + "window_type": { "type": "string", "enum": ["lifetime", "no_returns", "final_sale", "finite_window"], "description": "The type of return window." From 135ba78766cd5ae9b68f97ed24397004dbf714f4 Mon Sep 17 00:00:00 2001 From: Venkatesh Hodavdekar Date: Thu, 14 May 2026 14:13:14 +0000 Subject: [PATCH 03/11] refactor: inline Return Extension schemas and refine specification Consolidates the Return Extension domain schemas by embedding return_policy, return_method, and return_fee directly into inline $defs within return.json, eliminating redundant standalone files. Additionally, simplifies policy modeling by completely removing the brittle window_type property to rely natively on clean field semantics, converts logistics channels and fee structures to open strings for robust client parsing, makes policy correlation IDs optional, and adds a standard capability profile Discovery section to the specification documentation. --- docs/specification/return.md | 47 +++++++---- source/schemas/shopping/return.json | 84 ++++++++++++++++++- source/schemas/shopping/types/return_fee.json | 23 ----- .../schemas/shopping/types/return_method.json | 19 ----- .../schemas/shopping/types/return_policy.json | 39 --------- 5 files changed, 114 insertions(+), 98 deletions(-) delete mode 100644 source/schemas/shopping/types/return_fee.json delete mode 100644 source/schemas/shopping/types/return_method.json delete mode 100644 source/schemas/shopping/types/return_policy.json diff --git a/docs/specification/return.md b/docs/specification/return.md index 51883ce4f..087f92a5e 100644 --- a/docs/specification/return.md +++ b/docs/specification/return.md @@ -25,8 +25,7 @@ By exposing the return policy natively in the UCP schema, AI agents and platform 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. + * `return_days` — the number of days allowed for the return. * `methods[]` — permitted physical methods (in-store, by-mail, etc.) * `fee` — the cost structure for that specific method. @@ -34,37 +33,60 @@ This extension adds a `return_policies` field to Checkout containing: * `return_policies[0]` Standard Apparel * `line_item_ids` 👕👖 - * `window_type` = `finite_window` 🗓️ 30 Days + * `return_days` = 30 Days 🗓️ * `methods[0]` In-Store 🏬 * `fee` = `free` ✅ * `methods[1]` By Mail 📦 * `fee` = `fixed_fee` $5.00 💸 -* `return_policies[1]` Final Sale +* `return_policies[1]` Non-Returnable / Final Sale * `line_item_ids` ⌚ - * `window_type` = `final_sale` 🚫 * `exchanges_allowed` = `false` +## Discovery + +Businesses advertise return policy support in their profile by registering the extension under `capabilities`: + +```json +{ + "ucp": { + "version": "{{ ucp_version }}", + "capabilities": { + "dev.ucp.shopping.return": [ + { + "version": "{{ ucp_version }}", + "extends": ["dev.ucp.shopping.checkout"], + "spec": "https://ucp.dev/{{ ucp_version }}/specification/return", + "schema": "https://ucp.dev/{{ ucp_version }}/schemas/shopping/return.json" + } + ] + } + } +} +``` + +Platforms SHOULD check for this capability before attempting to render `return_policies` from a checkout response. + ## 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') }} +{{ extension_fields('return', 'return') }} ### Entities #### Return Policy -{{ schema_fields('types/return_policy', 'return') }} +{{ extension_schema_fields('return.json#/$defs/return_policy', 'return') }} #### Return Method -{{ schema_fields('types/return_method', 'return') }} +{{ extension_schema_fields('return.json#/$defs/return_method', 'return') }} #### Return Fee -{{ schema_fields('types/return_fee', 'return') }} +{{ extension_schema_fields('return.json#/$defs/return_fee', 'return') }} ## Rendering @@ -80,10 +102,9 @@ Return policies are designed for proactive disclosures by the merchant. Platform ### Business Responsibilities -**For `window_type`:** +**For `return_days`:** -* **MUST** accurately reflect the merchant's legal and commercial policy. -* **MUST** provide `return_days` if the type is `finite_window`. +* **MUST** accurately reflect the merchant's legal and commercial return window duration. **For `return_method.fee`:** @@ -110,7 +131,6 @@ In this example, apparel items have a standard window, while a custom item is fi { "id": "rp_apparel", "line_item_ids": ["shirt", "pants"], - "window_type": "finite_window", "return_days": 30, "exchanges_allowed": true, "methods": [ @@ -134,7 +154,6 @@ In this example, apparel items have a standard window, while a custom item is fi { "id": "rp_final_sale", "line_item_ids": ["custom_engraved_watch"], - "window_type": "final_sale", "exchanges_allowed": false } ] diff --git a/source/schemas/shopping/return.json b/source/schemas/shopping/return.json index 10295a03c..1f5d5dbb6 100644 --- a/source/schemas/shopping/return.json +++ b/source/schemas/shopping/return.json @@ -6,13 +6,91 @@ "description": "Extends Checkout with return policy support.", "$defs": { "return_policy": { - "$ref": "types/return_policy.json" + "title": "Return Policy", + "description": "Conditions, methods, timelines, and costs associated with returning physical items.", + "type": "object", + "required": [ + "line_item_ids" + ], + "properties": { + "id": { + "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." + }, + "return_days": { + "type": "integer", + "description": "Length of the merchant's return window in days, measured from the date of delivery. This is a policy statement — it communicates the window duration, not a pre-computed deadline." + }, + "exchanges_allowed": { + "type": "boolean", + "description": "Indicates whether the buyer can exchange the item." + }, + "methods": { + "type": "array", + "items": { + "$ref": "#/$defs/return_method" + }, + "description": "Permitted physical methods for returning the item, along with their associated fee structures." + } + } }, "return_method": { - "$ref": "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", + "description": "Physical return channel. Clients MUST tolerate unknown values. Well-known values: `in_store` (buyer returns at a retail location), `by_mail` (buyer ships item back via carrier), `kiosk` (buyer drops off at a self-service kiosk or designated drop point).", + "examples": [ + "in_store", + "by_mail", + "kiosk" + ] + }, + "fee": { + "$ref": "#/$defs/return_fee", + "description": "The cost structure associated with this specific return method." + } + } }, "return_fee": { - "$ref": "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", + "description": "Cost structure for this return method. Clients MUST tolerate unknown values. Well-known values: `free` (merchant covers return costs), `fixed_fee` (flat fee charged, see amount), `customer_responsibility` (buyer arranges and pays for return shipping).", + "examples": [ + "free", + "fixed_fee", + "customer_responsibility" + ] + }, + "amount": { + "$ref": "types/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')." + } + } }, "dev.ucp.shopping.checkout": { "title": "Checkout with Return", diff --git a/source/schemas/shopping/types/return_fee.json b/source/schemas/shopping/types/return_fee.json deleted file mode 100644 index d958012ea..000000000 --- a/source/schemas/shopping/types/return_fee.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$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"], - "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')." - } - } -} diff --git a/source/schemas/shopping/types/return_method.json b/source/schemas/shopping/types/return_method.json deleted file mode 100644 index 8ed1434a2..000000000 --- a/source/schemas/shopping/types/return_method.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "$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"], - "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." - } - } -} diff --git a/source/schemas/shopping/types/return_policy.json b/source/schemas/shopping/types/return_policy.json deleted file mode 100644 index fc2e24bdc..000000000 --- a/source/schemas/shopping/types/return_policy.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$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": { - "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." - }, - "return_days": { - "type": "integer", - "description": "Number of days allowed for a return, typically starting from the date of delivery. Required if category is finite_window." - }, - "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." - } - } -} From 28e20b0ec90d23fb8630cea54c422c33ee1d789f Mon Sep 17 00:00:00 2001 From: Venkatesh Hodavdekar Date: Mon, 18 May 2026 18:11:05 -0500 Subject: [PATCH 04/11] refactor: remove id field from checkout-time return policy --- .linkignore | 2 +- docs/specification/return.md | 155 +++++++++++++++------------- source/schemas/shopping/return.json | 29 +----- 3 files changed, 91 insertions(+), 95 deletions(-) diff --git a/.linkignore b/.linkignore index 2f00a358e..29fe84c6f 100644 --- a/.linkignore +++ b/.linkignore @@ -1,2 +1,2 @@ # Ignore ucp.dev dummy links -https://ucp\.dev/specification/reference\?v=2026-01-11 \ No newline at end of file +https://ucp\.dev/specification/reference\?v=2026-01-11 diff --git a/docs/specification/return.md b/docs/specification/return.md index 087f92a5e..80e09cadc 100644 --- a/docs/specification/return.md +++ b/docs/specification/return.md @@ -18,57 +18,66 @@ ## 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. +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. +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. - * `return_days` — the number of days allowed for the return. - * `methods[]` — permitted physical methods (in-store, by-mail, etc.) - * `fee` — the cost structure for that specific method. +- `return_policies[]` — conditions governed by the merchant for specific items. + - `return_days` — the number of days allowed for the return. + - `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` 👕👖 - * `return_days` = 30 Days 🗓️ - * `methods[0]` In-Store 🏬 - * `fee` = `free` ✅ - * `methods[1]` By Mail 📦 - * `fee` = `fixed_fee` $5.00 💸 -* `return_policies[1]` Non-Returnable / Final Sale - * `line_item_ids` ⌚ - * `exchanges_allowed` = `false` +- `return_policies[0]` Standard Apparel + - `line_item_ids` 👕👖 + - `return_days` = 30 Days 🗓️ + - `methods[0]` In-Store 🏬 + - `fee` = `free` ✅ + - `methods[1]` By Mail 📦 + - `fee` = `fixed_fee` $5.00 💸 +- `return_policies[1]` Non-Returnable / Final Sale + - `line_item_ids` ⌚ + - `exchanges_allowed` = `false` ## Discovery -Businesses advertise return policy support in their profile by registering the extension under `capabilities`: +Businesses advertise return policy support in their profile by registering the +extension under `capabilities`: ```json { - "ucp": { - "version": "{{ ucp_version }}", - "capabilities": { - "dev.ucp.shopping.return": [ - { - "version": "{{ ucp_version }}", - "extends": ["dev.ucp.shopping.checkout"], - "spec": "https://ucp.dev/{{ ucp_version }}/specification/return", - "schema": "https://ucp.dev/{{ ucp_version }}/schemas/shopping/return.json" + "ucp": { + "version": "{{ ucp_version }}", + "capabilities": { + "dev.ucp.shopping.return": [ + { + "version": "{{ ucp_version }}", + "extends": ["dev.ucp.shopping.checkout"], + "spec": "https://ucp.dev/{{ ucp_version }}/specification/return", + "schema": "https://ucp.dev/{{ ucp_version }}/schemas/shopping/return.json" + } + ] } - ] } - } } ``` -Platforms SHOULD check for this capability before attempting to render `return_policies` from a checkout response. +Platforms SHOULD check for this capability before attempting to render +`return_policies` from a checkout response. ## 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. +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 @@ -90,72 +99,78 @@ Return policies apply to physical items in a checkout session. Items not governe ## 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. +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. | +| 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 **For `return_days`:** -* **MUST** accurately reflect the merchant's legal and commercial return window duration. +- **MUST** accurately reflect the merchant's legal and commercial return window + duration. **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`. +- **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: +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. +- 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. +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"], - "return_days": 30, - "exchanges_allowed": true, - "methods": [ + "return_policies": [ { - "type": "in_store", - "fee": { - "type": "free", - "display_text": "Free In-Store Return" - } + "line_item_ids": ["shirt", "pants"], + "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" + } + } + ] }, { - "type": "by_mail", - "fee": { - "type": "fixed_fee", - "amount": 500, - "display_text": "Return Shipping Fee" - } + "line_item_ids": ["custom_engraved_watch"], + "exchanges_allowed": false } - ] - }, - { - "id": "rp_final_sale", - "line_item_ids": ["custom_engraved_watch"], - "exchanges_allowed": false - } - ] + ] } ``` diff --git a/source/schemas/shopping/return.json b/source/schemas/shopping/return.json index 1f5d5dbb6..b716e1187 100644 --- a/source/schemas/shopping/return.json +++ b/source/schemas/shopping/return.json @@ -9,14 +9,8 @@ "title": "Return Policy", "description": "Conditions, methods, timelines, and costs associated with returning physical items.", "type": "object", - "required": [ - "line_item_ids" - ], + "required": ["line_item_ids"], "properties": { - "id": { - "type": "string", - "description": "Unique identifier for the return policy." - }, "line_item_ids": { "type": "array", "items": { @@ -45,19 +39,12 @@ "title": "Return Method", "description": "The physical method through which the buyer can return the item.", "type": "object", - "required": [ - "type", - "fee" - ], + "required": ["type", "fee"], "properties": { "type": { "type": "string", "description": "Physical return channel. Clients MUST tolerate unknown values. Well-known values: `in_store` (buyer returns at a retail location), `by_mail` (buyer ships item back via carrier), `kiosk` (buyer drops off at a self-service kiosk or designated drop point).", - "examples": [ - "in_store", - "by_mail", - "kiosk" - ] + "examples": ["in_store", "by_mail", "kiosk"] }, "fee": { "$ref": "#/$defs/return_fee", @@ -69,18 +56,12 @@ "title": "Return Fee", "description": "The cost structure associated with a specific return method.", "type": "object", - "required": [ - "type" - ], + "required": ["type"], "properties": { "type": { "type": "string", "description": "Cost structure for this return method. Clients MUST tolerate unknown values. Well-known values: `free` (merchant covers return costs), `fixed_fee` (flat fee charged, see amount), `customer_responsibility` (buyer arranges and pays for return shipping).", - "examples": [ - "free", - "fixed_fee", - "customer_responsibility" - ] + "examples": ["free", "fixed_fee", "customer_responsibility"] }, "amount": { "$ref": "types/amount.json", From e22282613a1de8c497a870fa048c18b6da058667 Mon Sep 17 00:00:00 2001 From: Venkatesh Hodavdekar Date: Wed, 20 May 2026 15:10:57 -0500 Subject: [PATCH 05/11] refactor(return): address review feedback on Return Extension - Rename `return_days` to `return_period_in_days` for clarity. - Replace `exchanges_allowed` boolean with `supported_resolutions` flat array of strings to support richer outcomes (e.g. store credit, gift card). - Drop empty capability schemas (`dev.ucp.shopping.return`). - Update documentation and examples to match schema changes. --- docs/specification/return.md | 36 ++++++++++++++++++----------- source/schemas/shopping/return.json | 32 +++++++------------------ 2 files changed, 30 insertions(+), 38 deletions(-) diff --git a/docs/specification/return.md b/docs/specification/return.md index 80e09cadc..be487fe3e 100644 --- a/docs/specification/return.md +++ b/docs/specification/return.md @@ -30,7 +30,8 @@ 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. - - `return_days` — the number of days allowed for the return. + - `return_period_in_days` — the number of days allowed for the return. + - `supported_resolutions[]` — allowed outcomes (refund, exchange, etc.). - `methods[]` — permitted physical methods (in-store, by-mail, etc.) - `fee` — the cost structure for that specific method. @@ -38,14 +39,15 @@ This extension adds a `return_policies` field to Checkout containing: - `return_policies[0]` Standard Apparel - `line_item_ids` 👕👖 - - `return_days` = 30 Days 🗓️ + - `return_period_in_days` = 30 Days 🗓️ + - `supported_resolutions` = `["original_payment_method", "exchange"]` - `methods[0]` In-Store 🏬 - `fee` = `free` ✅ - `methods[1]` By Mail 📦 - `fee` = `fixed_fee` $5.00 💸 - `return_policies[1]` Non-Returnable / Final Sale - `line_item_ids` ⌚ - - `exchanges_allowed` = `false` + - `supported_resolutions` = `[]` ## Discovery @@ -105,19 +107,25 @@ 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. | +| Location | Field | Required | Purpose | +| ------------------- | ----------------------- | -------- | --------------------------------------------- | +| `return_policy` | `return_period_in_days` | No | Quantitative window for the return. | +| `return_policy` | `supported_resolutions` | No | Allowed outcomes (refund, exchange, etc.). | +| `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 -**For `return_days`:** +**For `return_period_in_days`:** - **MUST** accurately reflect the merchant's legal and commercial return window duration. +**For `supported_resolutions`:** + +- **MUST** list all resolutions supported by the merchant (e.g. if exchange is + supported it must be explicitly listed). + **For `return_method.fee`:** - **SHOULD** use `display_text` to explain the nature of the fee (e.g., "Prepaid @@ -132,8 +140,8 @@ 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. +- Use `return_period_in_days` to calculate and display the specific return + deadline based on the delivery date. ## Examples @@ -147,8 +155,8 @@ final sale. "return_policies": [ { "line_item_ids": ["shirt", "pants"], - "return_days": 30, - "exchanges_allowed": true, + "return_period_in_days": 30, + "supported_resolutions": ["original_payment_method", "exchange"], "methods": [ { "type": "in_store", @@ -169,7 +177,7 @@ final sale. }, { "line_item_ids": ["custom_engraved_watch"], - "exchanges_allowed": false + "supported_resolutions": [] } ] } diff --git a/source/schemas/shopping/return.json b/source/schemas/shopping/return.json index b716e1187..33cb0e8f6 100644 --- a/source/schemas/shopping/return.json +++ b/source/schemas/shopping/return.json @@ -18,13 +18,17 @@ }, "description": "Line items governed by this return policy, allowing distinct policies per item." }, - "return_days": { + "return_period_in_days": { "type": "integer", "description": "Length of the merchant's return window in days, measured from the date of delivery. This is a policy statement — it communicates the window duration, not a pre-computed deadline." }, - "exchanges_allowed": { - "type": "boolean", - "description": "Indicates whether the buyer can exchange the item." + "supported_resolutions": { + "type": "array", + "items": { + "type": "string", + "description": "The resolution method. Well-known values: `original_payment_method` (refund to original payment method), `store_credit` (refund for store credit), `gift_card` (refund as gift card), `exchange` (exchange for different item), `replacement` (replace with same item). Clients MUST tolerate unknown values." + }, + "description": "Supported outcomes for the return. An empty array `[]` explicitly indicates the items are non-returnable (final sale)." }, "methods": { "type": "array", @@ -94,26 +98,6 @@ } } ] - }, - "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" - } - ] - } } } } From 87a7de0be6a264b0caaf50464377857c529ec25c Mon Sep 17 00:00:00 2001 From: Venkatesh Hodavdekar Date: Fri, 22 May 2026 07:31:19 -0500 Subject: [PATCH 06/11] refactor(return): invert relationship to registry pattern and add reason field --- docs/specification/return.md | 106 ++++++++++++++++------------ source/schemas/shopping/return.json | 37 +++++++--- 2 files changed, 87 insertions(+), 56 deletions(-) diff --git a/docs/specification/return.md b/docs/specification/return.md index be487fe3e..ef2b17d79 100644 --- a/docs/specification/return.md +++ b/docs/specification/return.md @@ -27,27 +27,30 @@ 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: +This extension adds a `return_policies` registry to Checkout and a reference on line items: -- `return_policies[]` — conditions governed by the merchant for specific items. +- `line_items[].return_policy_id` — reference to the policy that applies to this item. +- `return_policies` — map of policy definitions keyed by ID, containing: - `return_period_in_days` — the number of days allowed for the return. - `supported_resolutions[]` — allowed outcomes (refund, exchange, etc.). - `methods[]` — permitted physical methods (in-store, by-mail, etc.) - `fee` — the cost structure for that specific method. + - `reason` — human-readable explanation of why this policy applies. **Mental model:** -- `return_policies[0]` Standard Apparel - - `line_item_ids` 👕👖 - - `return_period_in_days` = 30 Days 🗓️ - - `supported_resolutions` = `["original_payment_method", "exchange"]` - - `methods[0]` In-Store 🏬 - - `fee` = `free` ✅ - - `methods[1]` By Mail 📦 - - `fee` = `fixed_fee` $5.00 💸 -- `return_policies[1]` Non-Returnable / Final Sale - - `line_item_ids` ⌚ - - `supported_resolutions` = `[]` +- **Line Items:** + - 👕 (Shirt) -> `return_policy_id` = `"rp_standard"` + - 👖 (Pants) -> `return_policy_id` = `"rp_standard"` + - ⌚ (Watch) -> `return_policy_id` = `"rp_final_sale"` +- **Return Policies Registry:** + - `"rp_standard"`: + - `return_period_in_days` = 30 Days 🗓️ + - `supported_resolutions` = `["original_payment_method", "exchange"]` + - `methods`: In-Store (free) 🏬, By Mail ($5.00) 📦 + - `"rp_final_sale"`: + - `supported_resolutions` = `[]` (Final Sale) + - `reason` = "Clearance items are non-returnable." 🚫 ## Discovery @@ -107,12 +110,13 @@ requirements of a purchase before completion. ### Human-Readable Fields -| Location | Field | Required | Purpose | -| ------------------- | ----------------------- | -------- | --------------------------------------------- | -| `return_policy` | `return_period_in_days` | No | Quantitative window for the return. | -| `return_policy` | `supported_resolutions` | No | Allowed outcomes (refund, exchange, etc.). | -| `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. | +| Location | Field | Required | Purpose | +| ------------------- | ----------------------- | -------- | -------------------------------------------------- | +| `return_policy` | `return_period_in_days` | No | Quantitative window for the return. | +| `return_policy` | `reason` | No | Human-readable explanation of policy application. | +| `return_policy` | `supported_resolutions` | No | Allowed outcomes (refund, exchange, etc.). | +| `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 @@ -147,38 +151,50 @@ assurance: ### Mixed Cart -In this example, apparel items have a standard window, while a custom item is -final sale. +In this example, apparel items reference a standard policy, while a custom item references a final sale policy with a reason. ```json { - "return_policies": [ + "line_items": [ + { + "id": "shirt", + "return_policy_id": "rp_standard" + }, + { + "id": "pants", + "return_policy_id": "rp_standard" + }, + { + "id": "custom_engraved_watch", + "return_policy_id": "rp_final_sale" + } + ], + "return_policies": { + "rp_standard": { + "return_period_in_days": 30, + "supported_resolutions": ["original_payment_method", "exchange"], + "methods": [ { - "line_item_ids": ["shirt", "pants"], - "return_period_in_days": 30, - "supported_resolutions": ["original_payment_method", "exchange"], - "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" - } - } - ] + "type": "in_store", + "fee": { + "type": "free", + "display_text": "Free In-Store Return" + } }, { - "line_item_ids": ["custom_engraved_watch"], - "supported_resolutions": [] + "type": "by_mail", + "fee": { + "type": "fixed_fee", + "amount": 500, + "display_text": "Return Shipping Fee" + } } - ] + ] + }, + "rp_final_sale": { + "supported_resolutions": [], + "reason": "Custom engraved items are final sale." + } + } } ``` diff --git a/source/schemas/shopping/return.json b/source/schemas/shopping/return.json index 33cb0e8f6..0503045b6 100644 --- a/source/schemas/shopping/return.json +++ b/source/schemas/shopping/return.json @@ -9,15 +9,7 @@ "title": "Return Policy", "description": "Conditions, methods, timelines, and costs associated with returning physical items.", "type": "object", - "required": ["line_item_ids"], "properties": { - "line_item_ids": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Line items governed by this return policy, allowing distinct policies per item." - }, "return_period_in_days": { "type": "integer", "description": "Length of the merchant's return window in days, measured from the date of delivery. This is a policy statement — it communicates the window duration, not a pre-computed deadline." @@ -36,6 +28,10 @@ "$ref": "#/$defs/return_method" }, "description": "Permitted physical methods for returning the item, along with their associated fee structures." + }, + "reason": { + "type": "string", + "description": "Human-readable explanation of why this return policy applies. Helps platforms and agents explain policy provenance to buyers (e.g., 'Custom engraved items are non-returnable per our personalization policy'). Optional." } } }, @@ -88,12 +84,31 @@ "type": "object", "properties": { "return_policies": { - "type": "array", - "items": { + "type": "object", + "additionalProperties": { "$ref": "#/$defs/return_policy" }, - "description": "Return policies applicable to items in the checkout.", + "description": "Registry of return policies, keyed by policy ID. Line items reference these policies by key.", "ucp_request": "omit" + }, + "line_items": { + "type": "array", + "items": { + "allOf": [ + { + "$ref": "types/line_item.json" + }, + { + "type": "object", + "properties": { + "return_policy_id": { + "type": "string", + "description": "ID of the return policy that applies to this line item. Must reference a key in the top-level return_policies map." + } + } + } + ] + } } } } From e5990c85bfd2db07ab71ca641eb674953e80ee50 Mon Sep 17 00:00:00 2001 From: Venkatesh Hodavdekar Date: Fri, 22 May 2026 09:09:23 -0500 Subject: [PATCH 07/11] refactor(return): add behavior specifications and policy_url terms link --- docs/specification/return.md | 33 +++++++++++++++++++++++++++++ source/schemas/shopping/return.json | 5 +++++ 2 files changed, 38 insertions(+) diff --git a/docs/specification/return.md b/docs/specification/return.md index ef2b17d79..205eca37c 100644 --- a/docs/specification/return.md +++ b/docs/specification/return.md @@ -36,6 +36,16 @@ This extension adds a `return_policies` registry to Checkout and a reference on - `methods[]` — permitted physical methods (in-store, by-mail, etc.) - `fee` — the cost structure for that specific method. - `reason` — human-readable explanation of why this policy applies. + - `policy_url` — URL to the merchant's full return policy document. + +### Relationship to Other Extensions + +The Return Extension is independent of the Fulfillment Extension. Businesses MAY use information from `fulfillment` to compute return options (e.g., in-store return locations may correlate with fulfillment retail locations), but this extension does NOT model that relationship. Platforms MUST NOT assume any structural correlation between return methods and fulfillment methods. + +### Scope and Future Directions + +- **Pre-Purchase Disclosure Only**: This extension addresses pre-purchase disclosure of return policy only. Post-purchase return initiation, label generation, refund processing, and exchange execution are out of scope. A future `dev.ucp.shopping.return_action` extension may address these workflows; implementers MUST NOT extend this schema for those purposes. +- **Product-Level Returns**: A return policy is fundamentally a property of a product (or its category), not a property of a checkout. A separate Product or Catalog capability could expose return policies at the catalog level in the future. For v1, checkout-time exposure is used for simplicity. **Mental model:** @@ -114,12 +124,17 @@ requirements of a purchase before completion. | ------------------- | ----------------------- | -------- | -------------------------------------------------- | | `return_policy` | `return_period_in_days` | No | Quantitative window for the return. | | `return_policy` | `reason` | No | Human-readable explanation of policy application. | +| `return_policy` | `policy_url` | No | URL to the merchant's full return policy document. | | `return_policy` | `supported_resolutions` | No | Allowed outcomes (refund, exchange, etc.). | | `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 +**For Policy Locking:** + +- **MUST** honor the return policy that was in effect at the time of order placement; that policy MUST be sourced from the Order resource (when available) rather than recomputed from current merchant rules. The return policy returned at checkout response time is the policy in effect for the buyer at that moment. + **For `return_period_in_days`:** - **MUST** accurately reflect the merchant's legal and commercial return window @@ -147,6 +162,23 @@ assurance: - Use `return_period_in_days` to calculate and display the specific return deadline based on the delivery date. +**For Partial Quantity Returns:** + +- **SHOULD** assume return policies apply to individual units within a line item. Unless specified otherwise (e.g., for bundled items), any subset of quantities is subject to the same return policy. + +**For Absent Policy Data:** + +- **MUST NOT** infer items are non-returnable if the Return capability is advertised in discovery but `return_policies` is absent or empty in a checkout response. Platforms SHOULD fall back to general merchant return info (e.g., via the merchant's profile URL). + +**Interpretation of Policy States:** + +Platforms MUST interpret combinations of `methods` and `return_period_in_days` as follows: + +- `methods` absent or empty (`[]`): Treat as "no return methods supported" (i.e., non-returnable/final sale). +- `return_period_in_days` is `0`: Treat as "non-returnable" (final sale). +- `return_period_in_days` absent, but `methods` present: Treat as "no time limit on returns". +- `return_period_in_days` present, but `methods` absent/empty: Ambiguous. Interpret as returnable, but client/buyer must arrange their own return shipment, or consult the full policy link if provided. + ## Examples ### Mixed Cart @@ -172,6 +204,7 @@ In this example, apparel items reference a standard policy, while a custom item "return_policies": { "rp_standard": { "return_period_in_days": 30, + "policy_url": "https://example.com/returns", "supported_resolutions": ["original_payment_method", "exchange"], "methods": [ { diff --git a/source/schemas/shopping/return.json b/source/schemas/shopping/return.json index 0503045b6..8ef549a3b 100644 --- a/source/schemas/shopping/return.json +++ b/source/schemas/shopping/return.json @@ -32,6 +32,11 @@ "reason": { "type": "string", "description": "Human-readable explanation of why this return policy applies. Helps platforms and agents explain policy provenance to buyers (e.g., 'Custom engraved items are non-returnable per our personalization policy'). Optional." + }, + "policy_url": { + "type": "string", + "format": "uri", + "description": "URL to the merchant's full return policy document. Platforms SHOULD surface this link alongside the structured policy data so buyers can review complete terms." } } }, From 5ee68ab557c2a59a263bad20b199722dce4ef46c Mon Sep 17 00:00:00 2001 From: Venkatesh Hodavdekar Date: Mon, 15 Jun 2026 15:25:58 -0500 Subject: [PATCH 08/11] fix: address review comments on return schema and specification --- docs/specification/return.md | 5 +++++ source/schemas/shopping/return.json | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/specification/return.md b/docs/specification/return.md index 205eca37c..9496b676e 100644 --- a/docs/specification/return.md +++ b/docs/specification/return.md @@ -131,6 +131,10 @@ requirements of a purchase before completion. ### Business Responsibilities +**For Policy References:** + +- **MUST** ensure every `return_policy_id` on a line item resolves to a key present in the top-level `return_policies` registry. + **For Policy Locking:** - **MUST** honor the return policy that was in effect at the time of order placement; that policy MUST be sourced from the Order resource (when available) rather than recomputed from current merchant rules. The return policy returned at checkout response time is the policy in effect for the buyer at that moment. @@ -169,6 +173,7 @@ assurance: **For Absent Policy Data:** - **MUST NOT** infer items are non-returnable if the Return capability is advertised in discovery but `return_policies` is absent or empty in a checkout response. Platforms SHOULD fall back to general merchant return info (e.g., via the merchant's profile URL). +- **MUST** treat unresolved policy references (`return_policy_id` without a matching key in `return_policies`) as having no stated return policy (per the Absent Policy Data rule) and **MUST NOT** infer the item is non-returnable. **Interpretation of Policy States:** diff --git a/source/schemas/shopping/return.json b/source/schemas/shopping/return.json index 8ef549a3b..bc212084c 100644 --- a/source/schemas/shopping/return.json +++ b/source/schemas/shopping/return.json @@ -12,7 +12,8 @@ "properties": { "return_period_in_days": { "type": "integer", - "description": "Length of the merchant's return window in days, measured from the date of delivery. This is a policy statement — it communicates the window duration, not a pre-computed deadline." + "minimum": 0, + "description": "Length of the merchant's return window in days, measured from the date of delivery. This is a policy statement — it communicates the window duration, not a pre-computed deadline. A value of 0 explicitly indicates the items are non-returnable (final sale)." }, "supported_resolutions": { "type": "array", @@ -108,7 +109,8 @@ "properties": { "return_policy_id": { "type": "string", - "description": "ID of the return policy that applies to this line item. Must reference a key in the top-level return_policies map." + "description": "ID of the return policy that applies to this line item. Must reference a key in the top-level return_policies map.", + "ucp_request": "omit" } } } From 374644741cac83833c907fc6d4ca7858378d1e14 Mon Sep 17 00:00:00 2001 From: Venkatesh Hodavdekar Date: Tue, 16 Jun 2026 09:35:12 -0500 Subject: [PATCH 09/11] docs(return): use encompassing term platforms for architectural consistency --- docs/specification/return.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/specification/return.md b/docs/specification/return.md index 9496b676e..dfc96821c 100644 --- a/docs/specification/return.md +++ b/docs/specification/return.md @@ -22,8 +22,8 @@ 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 +By exposing the return policy natively in the UCP schema, 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. From c53150ef84423fdf563e5c8b19531ee8a52db990 Mon Sep 17 00:00:00 2001 From: Venkatesh Hodavdekar Date: Tue, 16 Jun 2026 10:08:37 -0500 Subject: [PATCH 10/11] docs(return): add normative guidance for contextual policy accuracy --- docs/specification/return.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/specification/return.md b/docs/specification/return.md index dfc96821c..da597185e 100644 --- a/docs/specification/return.md +++ b/docs/specification/return.md @@ -135,6 +135,10 @@ requirements of a purchase before completion. - **MUST** ensure every `return_policy_id` on a line item resolves to a key present in the top-level `return_policies` registry. +**For Contextual Accuracy:** + +- **SHOULD** use contextual information available in the customer's checkout session (e.g., provisional buyer location signals such as shipping destination) to provide the most accurate policy information possible rather than relying on static defaults. + **For Policy Locking:** - **MUST** honor the return policy that was in effect at the time of order placement; that policy MUST be sourced from the Order resource (when available) rather than recomputed from current merchant rules. The return policy returned at checkout response time is the policy in effect for the buyer at that moment. From 5768e2eeb830181d60c67cf410349bd290430d2a Mon Sep 17 00:00:00 2001 From: Venkatesh Hodavdekar Date: Tue, 16 Jun 2026 10:11:17 -0500 Subject: [PATCH 11/11] docs(return): clarify supported_resolutions scope to checkout line items --- docs/specification/return.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/specification/return.md b/docs/specification/return.md index da597185e..7ece6a868 100644 --- a/docs/specification/return.md +++ b/docs/specification/return.md @@ -150,8 +150,7 @@ requirements of a purchase before completion. **For `supported_resolutions`:** -- **MUST** list all resolutions supported by the merchant (e.g. if exchange is - supported it must be explicitly listed). +- **MUST** list all resolutions applicable to the `line_items` in the checkout session governed by that policy (e.g., if exchange is supported for those items, it must be explicitly listed). **For `return_method.fee`:**