Since 23 October, UCP has a standard way for a store to tell an agent its return policy. The Return Policy Extension, dev.ucp.shopping.policy.return, was merged into the protocol's main branch that evening after three months of review. It gives return windows, refund types, return channels and fees a fixed, machine-readable shape. That shape travels with the product from catalog search through cart and checkout to the order.
It matters because returns are one of the few things a shopper asks before nearly every purchase, and until now an agent could only answer by reading a policy page. Policy pages are written for people. They mix the standard terms, final-sale exceptions and category rules in one block of prose, and an agent cannot tell which rule covers the item in the cart. The new extension lets the store say it once, per item, in fields an agent can act on.
What is the UCP Return Policy Extension?
The UCP Return Policy Extension is a policy type that lets a business state its return terms as structured data on any UCP response that carries policies. It covers four things:
- Eligibility: whether the item can be returned, and for how long.
- Resolutions: what the buyer gets back, such as a refund to the original payment method, store credit or an exchange.
- Methods: how the item can come back, such as in store, by mail or at a kiosk.
- Fees: what a return can cost, such as restocking, return shipping or handling.
It sits on the core policies[] primitive, which shipped in release 2026-08-25 with "return" already listed as a well-known type and an empty body. This extension fills in that body. The primitive keeps doing the targeting, the precedence between overlapping policies and the buyer-facing disclosures, so none of that is redefined here.
The extension is on main and in the draft specification on ucp.dev. It is not yet in a tagged release. The September Tech Council planning named returns as one of the pillars for the next release, expected early December or January.
How a store declares it
A business advertises support in its UCP profile, and can choose which surfaces carry return terms:
{
"ucp": {
"capabilities": {
"dev.ucp.shopping.policy.return": [
{
"version": "<release that includes it>",
"extends": [
"dev.ucp.shopping.catalog.search",
"dev.ucp.shopping.catalog.lookup",
"dev.ucp.shopping.cart",
"dev.ucp.shopping.checkout",
"dev.ucp.shopping.order"
]
}
]
}
}
}
A store may extend only some of these. A platform that has not negotiated the extension still shows the policy from its plain description, because the structured body is purely additive. Nothing breaks for an agent that ignores it.
A real policy, field by field
Here is the specification's own checkout example. It states a store-wide default once, then overrides it for one engraved item:
[
{
"type": "dev.ucp.shopping.policy.return",
"description": {
"plain": "30-day returns from delivery. Refund to original payment or exchange. No return shipping cost in store; $5 by mail. 15% restocking fee on opened items."
},
"eligibility": { "type": "window", "duration": "P30D", "anchor": "delivered" },
"supported_resolutions": ["original_payment_method", "exchange"],
"methods": ["in_store", "by_mail"],
"fees": [
{ "type": "dev.ucp.shopping.fee.restocking", "basis_points": 1500, "display_text": "15% restocking fee on opened items" },
{ "amount": 0 },
{ "type": "dev.ucp.shopping.fee.return_shipping", "methods": ["by_mail"], "amount": 500, "display_text": "Return shipping" }
],
"url": "https://example.com/returns"
},
{
"type": "dev.ucp.shopping.policy.return",
"description": { "plain": "This engraved item is final sale and cannot be returned." },
"applies_to": ["$.line_items[2]"],
"eligibility": { "type": "not_accepted" },
"url": "https://example.com/returns#final-sale"
}
]
Eligibility: three explicit states
eligibility.type takes one of three well-known values:
| Value | Means |
|---|---|
window |
Returns are accepted for at least duration after the anchor event. Both are required. |
unlimited |
No time limit. Other conditions, such as tags attached, can still apply. |
not_accepted |
The store's policy accepts no returns of these items, for example final sale. |
Leaving eligibility out means the store has not stated it structurally. It never means "returns at any time" and never means "no returns". An agent must not guess either way, and falls back to the description.
The anchor says when the clock starts: purchased, fulfilled (handed to a carrier, ready for pickup, or made available) or delivered. There is no default, because stores genuinely differ here. duration is an ISO 8601 duration, so P30D is 30 days and PT30M is 30 minutes, which the review thread gave as the case for perishables.
The store states the deadline, the agent does no date maths
The most important design choice is that the agent never computes a return-by date. Before delivery, an agent shows the terms, such as "30 days from delivery". Once the anchor event has happened, the store adds the actual deadline as expires_at, usually on the order:
"eligibility": {
"type": "window",
"duration": "P30D",
"anchor": "delivered",
"expires_at": "2026-04-01T18:00:00Z"
}
The agent can then say "Return by 1 April, 6pm UTC". The specification forbids it from showing a later deadline, for example by showing only the date, which reads as the end of that day. It also forbids presenting a date it calculated itself.
This came out of the last review round. A first attempt tried to make "30 days from delivery" computable by every platform, and, in the words of the revision that replaced it, "it quickly unravelled": orders have no placement timestamp, deliveries split, and every platform would need its own rules for days and time zones. So the store states the deadline, the same way it already states totals.
Final sale is one signal
To mark an item as non-returnable, a store sets eligibility.type to not_accepted on a policy scoped to that item. It may not also carry resolutions, methods or fees, and the schema rejects the combination. An empty list of methods does not mean "no returns" either. There is exactly one way to say final sale.
If the store needs the buyer to see that warning, it adds a disclosure message targeting the item:
{
"type": "warning",
"code": "dev.ucp.shopping.policy.return",
"path": "$.line_items[2]",
"presentation": "disclosure",
"content": "This engraved item is final sale and cannot be returned."
}
Fees: one typed list, scoped by channel
fees is a list. Each entry names what the charge is for and, optionally, which return channels it applies to. Three kinds are well known:
| Fee type | Charged per |
|---|---|
dev.ucp.shopping.fee.restocking |
Unit returned |
dev.ucp.shopping.fee.return_shipping |
Return shipment |
dev.ucp.shopping.fee.handling |
Return request |
A store can add its own kind, such as an eco packaging fee, as long as it gives display_text so any agent can show it. The amount is read from which fields are present: amount is a flat charge, basis_points is a percentage (1500 is 15 percent), and max_amount caps a percentage. An entry with neither is charged at actual cost.
Channel scoping lets a store say "free in store, $5 by mail" without two separate policies. An entry that names a channel beats one that doesn't. So { "methods": ["in_store"], "amount": 0 } makes in-store returns free even when a restocking fee applies everywhere else.
Two rules protect the buyer. First, absence never means free: only amount: 0 states that nothing is charged, and an agent must not say "no restocking fee" because a fee is missing. Second, fees are estimated maximums. A store must not state a fee lower than its policy charges, and an agent must not present a fee as the final charge for a particular return, because condition, carrier rates and how discounts are refunded can change it.
Defaults and overrides
A store states one default policy with no applies_to, then adds targeted overrides only for the exceptions, such as a final-sale item or a category with a longer window. The narrowest matching policy wins. An override inherits nothing from the default, so the store has to restate every term that still applies. A term the override leaves out, including fees, is simply not stated for those items.
Guarantees, not the whole policy
eligibility, supported_resolutions and methods are guarantees. They must hold for every eligible return in scope, so a store leaves a field out rather than filling it with something that is only sometimes true. Conditions such as "unopened" or "tags attached" go in the description. The description may state better terms than the fields, never worse.
The window is also a floor, not a ceiling. An agent must not tell a buyer an item can no longer be returned just because the window has passed, since the store's description may allow later returns. Only not_accepted says no.
It lines up with what stores already publish
The structure was built to match the two return-policy models stores already fill in. The revision notes say the three eligibility states "map one-to-one" to Google Merchant Center's return policy settings and to schema.org:
UCP eligibility.type |
Google Merchant API | schema.org |
|---|---|---|
window |
NUMBER_OF_DAYS_AFTER_DELIVERY |
MerchantReturnFiniteReturnWindow |
unlimited |
LIFETIME_RETURNS |
MerchantReturnUnlimitedWindow |
not_accepted |
NO_RETURNS |
MerchantReturnNotPermitted |
For a store, this means the return terms already entered in Merchant Center or marked up on product pages can be carried into UCP responses without being re-thought. It also connects to the UCP integration hub in Merchant Center, where Google's merchant setup already asks for return terms.
How it got here
The extension took three months and three designs:
- July: an early standalone proposal (#257) gave line items a
return_policy_id. Thepolicies[]primitive (#572, merged 23 July) replaced that with general targeting, and the return type was deliberately deferred to its own pull request. - 24 July to October: Amit Handa opened #634 to define the return body. It went through repeated review rounds and was approved by Greg Smith and Jing Li (Google) on 2 October.
- 22 October: Ilya Grigorik (Shopify) folded in #966, which made eligibility explicit, moved deadlines to the store, and replaced two separate fee fields with the typed list.
- 23 October: the Shopping Tech Council agreed to merge, according to the draft meeting minutes, and Ilya Grigorik merged it that evening.
Outside contributors shaped it too. Implementers tested the shape against real policies in the thread, and one group published three documented marketplace cases as a community discussion at the maintainers' request, as input for extraction guidance.
What it deliberately leaves out
The pull request lists what is out of scope:
- Running a return: issuing a return authorisation, generating labels, executing the refund and capturing a return reason. The pull request names that as a separate future extension.
- Tiered cancellation schedules, such as stepped refunds for bookings. Lodging keeps its own cancellation policy.
- Per-channel windows, per-channel resolutions and per-resolution fees, such as "30 days by mail, 60 in store" or "exchanges in store only". These are listed as future additions that fit the current shape without breaking it.
Try it on a test store
The extension is on main, not in a release, so no live store declares it yet. To see it working end to end, we added it to UCP Sandbox, our set of test stores. The Flowers store's returns host, flowers-returns.ucpsandbox.com, declares dev.ucp.shopping.policy.return and states its terms on catalog, cart, checkout and order responses. The ordinary Flowers store doesn't declare it, so you can compare the two.
Its policy uses each part of the extension:
- A default. Fresh flowers: 48 hours from delivery, refund or replacement, no fee. It has no
applies_to, so it covers every item no override covers. - An override. Vases, accessories and dried flowers: 30 days from delivery, refund to the card, free in a shop or by mail with a $4.95
return_shippingfee scoped toby_mail. - Final sale. Made-to-order wedding pieces are
not_accepted, with amessages[]warning (presentation: disclosure) on the checkout line.
A catalog lookup for a vase and a bouquet returns the default and the override, targeted at the vase by path:
POST https://flowers-returns.ucpsandbox.com/ucp/catalog/lookup
UCP-Agent: profile="https://your-agent.example/profile.json"
{"ids": ["vase_glass_cylinder", "bouquet_roses"]}
{
"type": "dev.ucp.shopping.policy.return",
"applies_to": ["$.products[1]"],
"eligibility": { "type": "window", "duration": "P30D", "anchor": "delivered" },
"supported_resolutions": ["original_payment_method"],
"methods": ["in_store", "by_mail"],
"fees": [
{ "amount": 0 },
{ "type": "dev.ucp.shopping.fee.return_shipping", "methods": ["by_mail"], "amount": 495 }
]
}
Here's what that looks like to a buyer. We asked an agent in UCP Playground (Claude Sonnet 5) about returns before buying anything: "can I return them if I change my mind, how long do I have, and does it cost anything?" It searched the store and answered from the structured terms: 30 days for the vase, free in a shop or $4.95 by mail, refund to the card; for the roses, no change-of-mind returns, with 48 hours from delivery to report damage.

The trace shows one detail worth knowing. Before it searched, the model reasoned that the store had no policy tool, so it couldn't confirm the terms. It didn't need one: the terms came back on the products. Agent builders should expect return terms on catalog, cart and checkout responses, not behind a separate tool.
Before delivery, an order carries the same terms and no deadline. Once it's delivered, each window gets an expires_at the store worked out, and the agent quotes a date and does no maths. We placed a test order for the vase, the roses and a made-to-order bridal bouquet. It was delivered at 15:07:30 UTC on 26 October, and the order now says 2026-10-28T15:07:30Z for the flowers and 2026-11-25T15:07:30Z for the vase, with not_accepted on the bouquet. The store's own order page shows the buyer the same dates, and marks the bridal bouquet as final sale.
Responses negotiated at release 2026-04-08, which has no policies[], get none of this.
Every policy and every whole response we built was checked against the draft policy_return.json schema before it went live.
What it means for stores and agent builders
For stores, return terms become something an agent can quote correctly at the moment of purchase, not something it paraphrases from a page. The practical work is to state a default policy, mark final-sale items with not_accepted, and add expires_at on orders once items are delivered. Stores on a hosted platform will likely get this from the platform rather than building it.
For agent builders, the rules are mostly about restraint. Show expires_at when it is there and the terms when it is not. Never compute a deadline. Never read a missing field as free or as returnable. Surface the policy url next to the structured terms so the buyer can read the full policy.
It also closes part of a gap we noted in September. When Shopify retired the old Storefront MCP cart tools, the policies and FAQ tool had no UCP counterpart. Structured return terms, together with the ask capability for free-form questions, now give agents two UCP routes to the same answers.
What we'll watch
- The release tag. Returns is on
main, not in a release. The next tagged release is when stores can declare it against a stable version. - The first live UCP profiles that declare
dev.ucp.shopping.policy.return. We'll report when they appear, and whether they stateexpires_aton orders. - The post-purchase returns extension the pull request names as future work, which is where agents would actually start a return.
FAQ
What is dev.ucp.shopping.policy.return?
It is the UCP policy type for return terms. A business uses it to state, in structured fields, whether an item can be returned, for how long, for what kind of refund, through which channels, and at what cost. It was merged into the UCP specification's main branch on 23 October 2026.
Is the UCP return policy extension in a released version?
Not yet. It is on main and in the draft specification on ucp.dev. The latest tagged release is 2026-08-25, which has the underlying policies[] primitive but not the return body. The next release is expected in early December or January.
How does a store mark an item as final sale in UCP?
It adds a return policy scoped to that item with "eligibility": { "type": "not_accepted" }, and no resolutions, methods or fees. If the buyer must be shown the warning, the store also adds a disclosure message targeting the item.
Does an agent calculate the return deadline?
No. Before delivery it shows the terms, such as "30 days from delivery". Once the clock has started, the store states the deadline as expires_at and the agent shows that. The specification forbids the agent from presenting a date it worked out itself.
What does a missing fee mean?
It means the fee is not stated, not that it is free. Only an entry with amount: 0 says nothing is charged. Fees that are stated are estimated maximums, not the final charge for a particular return.
How does UCP's return policy compare with schema.org and Google Merchant Center?
The three eligibility states map one-to-one: window, unlimited and not_accepted correspond to schema.org's finite, unlimited and not-permitted return windows, and to Google Merchant Center's days-after-delivery, lifetime and no-returns settings.
Can an agent start a return with this extension?
No. This extension only states terms. Return authorisations, labels and refunds are out of scope and are named as a separate future extension.
About UCP Checker
UCP Checker is the independent validation and observability layer for the Universal Commerce Protocol. We crawl, validate and grade every public UCP manifest we can find, run the merchant directory, the UCP Score, live adoption stats, the vertical guides and the authority-bound vendor map, and track the spec as it evolves so you don't have to — measured from two vantages (what a business declares, and what actually happens when an agent transacts), the same way for everyone, without picking winners.
Sources
- feat: add return policy extension (dev.ucp.shopping.policy.return), #634, UCP repository, merged 23 October 2026
- feat(return): explicit eligibility, business-stated deadlines, typed fees, #966, UCP repository, merged into #634 on 22 October 2026
- Return Policy Extension and policy_return.json, UCP specification, main branch
- Return Policy Extension, draft specification, ucp.dev
- feat: add policies[] to cart, checkout, catalog, and order, #572, UCP repository, merged 23 July 2026
- UCP Shopping Tech Council meeting, 23 October 2026, draft minutes, open pull request
- Marketplace return-policy extraction cases, discussion #961, UCP repository, 15 October 2026
- accounts.onlineReturnPolicies, Google Merchant API reference
- MerchantReturnPolicy, schema.org
Related coverage: Shopify Storefront MCP after August 31 · UCP's ask capability, merged · Google Merchant Center's UCP integration hub · State of agentic commerce, September 2026
