Return Policy Extension¶
Overview¶
The Return Policy Extension defines the dev.ucp.shopping.policy.return policy
type on the core policies[] primitive.
It adds machine-readable return terms to policies that carry this type, from
catalog through cart, checkout, and order, so platforms and agents can answer
questions like "How long do I have to return this?", "Do I get my money back or
store credit?", "Where and at what cost do I send it back?", and "Can I return
this at all?" without leaving to read a policy page.
Policy pages often lack product-level granularity: one page covers the standard
terms, final-sale items, category exceptions, and marketplace sellers in prose,
leaving buyers and agents unable to tell which terms govern a given item.
Because each return policy targets items through applies_to, the terms a
platform reads for an item are the ones that apply to it.
Key features:
- A return eligibility statement (
eligibility): a minimum return window with an explicit anchor, no time limit, or returns not accepted - What the buyer receives on an accepted return (
supported_resolutions) - Return channels (
methods) - Return fees, each typed by what it is for and optionally scoped to channels
(
fees)
Dependencies:
- The core
policies[]primitive (see Policies). - One or more of the parent capabilities this type extends: Catalog Search, Catalog Lookup, Cart, Checkout, or Order.
This extension only structures the type-specific body. The policies[]
container, applies_to targeting, same-type precedence, and buyer-facing
disclosure through messages[] are defined once by the primitive and are
unchanged here.
Discovery¶
Businesses advertise return policy support in their profile. The type extends
any surface that carries policies[]:
{
"ucp": {
"version": "draft",
"capabilities": {
"dev.ucp.shopping.policy.return": [
{
"version": "draft",
"extends": [
"dev.ucp.shopping.catalog.search",
"dev.ucp.shopping.catalog.lookup",
"dev.ucp.shopping.cart",
"dev.ucp.shopping.checkout",
"dev.ucp.shopping.order"
],
"spec": "https://ucp.dev/draft/specification/shopping/extensions/return-policy",
"schema": "https://ucp.dev/draft/schemas/shopping/policy_return.json"
}
]
}
}
}
A business MAY advertise support on a subset of these surfaces. A platform
that has negotiated this type validates the return body and MAY reason over
its structured fields (for example, presenting a return deadline) only when the
body passes schema validation. A platform that has not negotiated it still
renders the policy from the base type and description, because the return
body is purely additive.
Schema¶
When this type is active, a policies[] entry whose type is
dev.ucp.shopping.policy.return MAY carry the following fields in addition
to the base type, description, applies_to, and url. Each top-level
field is optional, and an omitted field means the term is not stated
structurally; a return policy with no structured fields is still presentable
from its description.
Return Policy¶
Return terms for a dev.ucp.shopping.policy.return policy.
| Name | Type | Requirement | Description |
|---|---|---|---|
| eligibility | object | Optional | Structured return eligibility statement. Omitted means eligibility is not stated structurally, not that returns are unlimited or not accepted. When type is not_accepted, the business's return policy does not accept returns of the covered items, and supported_resolutions, methods, and fees MUST be omitted. |
| supported_resolutions | Array[string] | Optional | Return outcomes guaranteed for every eligible return, as an unordered set; not an exhaustive list. Well-known values: original_payment_method, store_credit, gift_card, exchange, replacement. |
| methods | Array[string] | Optional | Return channels guaranteed for every eligible return, as an unordered set; not an exhaustive list. Well-known values: in_store, by_mail, kiosk. Omit when channels are not stated structurally. |
| fees | Array[object] | Optional | Estimated maximum charges on a return, each typed by what it is for and optionally scoped to channels; not an exhaustive list. Omitted means fees are not stated structurally, not that none are charged. |
Return Eligibility¶
An open return eligibility statement. window states a guaranteed minimum period after an anchor event and, once resolved, the instant it ends; expiration does not signal non-returnability. unlimited states no time restriction, not unconditional eligibility. not_accepted states that the business's return policy does not accept returns of the covered items; it does not describe remedies under applicable law or third-party guarantees. Unknown types establish no eligibility inference; consumers may use the base policy's description.
| Name | Type | Requirement | Description |
|---|---|---|---|
| type | string | Required | Eligibility kind. Well-known values: window, unlimited, not_accepted. Unknown values establish no eligibility inference. |
| duration | Duration | Optional | Length of the return window after the anchor event. It states the term for presentation; the Business resolves the deadline as expires_at. |
| anchor | string | Optional | Event the window is measured from. Well-known values: purchased, fulfilled, delivered. |
| expires_at | string | Optional | RFC 3339 instant at which the window ends, resolved by the Business once the anchor event has occurred (e.g., on an order after delivery). When covered quantities reach the anchor at different times, it states the earliest of their deadlines. An otherwise eligible return requested no later than this instant is accepted. |
Return Fee¶
Each entry in fees uses this shape.
A charge on a return, typed by what it is for, as an estimated maximum. amount states a flat charge, where 0 means nothing is charged; basis_points states a percentage, optionally capped by max_amount; with neither, the fee is charged at its actual cost and no maximum is stated.
| Name | Type | Requirement | Description |
|---|---|---|---|
| type | Reverse Domain Name | Optional | Fee kind, as a reverse-domain name. Well-known values: dev.ucp.shopping.fee.restocking (a deduction from the refund, charged per one whole quantity_unit.unit returned, for example per lb, or per each when quantity_unit is absent), dev.ucp.shopping.fee.return_shipping (the cost of shipping the return, charged per return shipment), dev.ucp.shopping.fee.handling (charged per return request). Vendor kinds use the vendor's own namespace (e.g., com.example.fee.eco) and are charged per return request. A vendor fee charged on another basis, such as per item, omits amount and states its terms in display_text. A fee with an unknown type MUST carry display_text. Omitted only on an entry with amount: 0, which states that the business charges no fee of any kind in its scope, including vendor kinds and kinds defined later. |
| methods | Array[string] | Optional | Return channels this fee applies to; omitted means every channel. A channel need not be listed in the policy's methods. When more than one entry applies to a fee on a channel, an entry that lists the channel takes precedence over one that does not; among entries that both list the channel or both do not, an entry with a type takes precedence over one without. |
| amount | Amount | Optional | Estimated maximum flat charge, in ISO 4217 minor units of the currency the covered items are priced in, charged per the unit of the fee type. 0 states that nothing is charged. Mutually exclusive with basis_points. |
| basis_points | integer | Optional | Estimated maximum charge in basis points (hundredths of a percent; 1250 = 12.5%) of the amount paid for the returned quantity, after item-level discounts and excluding tax, shipping, and other fees. On catalog surfaces, where no amount has been paid, it applies to the covered product's price. Mutually exclusive with amount. |
| max_amount | Amount | Optional | Cap on a basis_points charge: the charge is at most this amount, in ISO 4217 minor units of the currency the covered items are priced in, per the unit of the fee type. Requires basis_points. |
| display_text | string | Optional | Human-readable fee label, naming any condition under which the fee applies (e.g., '15% restocking fee on opened items'). Required when type is not a well-known fee kind, and when the fee has neither amount nor basis_points. |
Return terms¶
Eligibility¶
eligibility states whether, and for how long, the covered items can be
returned. Its type is required and is an open vocabulary. The well-known
kinds are:
type |
States that |
|---|---|
window |
Otherwise eligible returns are accepted for at least duration from the event named by anchor; both are required (see Window and anchor). |
unlimited |
Otherwise eligible returns are subject to no time restriction. |
not_accepted |
The business's return policy does not accept returns of the covered items (see Non-returnable items). |
duration, anchor, and expires_at have meaning only on a window, so a
business MUST NOT carry them on an unlimited or not_accepted
eligibility; the schema rejects the combination.
not_accepted states that the business's return policy accepts no returns of
the covered items. window and unlimited state a time bound, not a verdict on
a particular return: window guarantees a period for returns that are otherwise
eligible, and unlimited removes the time restriction and nothing else, so
neither kind alone establishes that a particular attempted return meets every
requirement. A business MAY describe eligibility requirements beyond the
time bound, such as tags attached or original packaging, in description.
An omitted eligibility means eligibility is not stated structurally; it
states neither that returns are unrestricted nor that they are unavailable, and
the description carries the terms. A platform MUST NOT infer eligibility
from an omitted eligibility or from an eligibility type it does not
recognize, for example by presenting the items as returnable without a time
limit or as not returnable; it MAY present the policy from its
description.
Window and anchor¶
A window eligibility states the length of the return window as a policy
statement, not a live countdown. It is a guaranteed minimum for otherwise
eligible returns: a return requested within the window is accepted through a
listed method, with a listed resolution that method accepts. duration and
anchor state the terms, for example "30 days from delivery"; once the anchor
event has occurred, the business resolves the guaranteed deadline and states it
as expires_at. A return requested no later than expires_at is within the
window; later steps, such as sending the item back within a set period, are
eligibility requirements a business MAY describe in description. The
window is a floor, not a ceiling: its expiry does not establish that an item is
non-returnable (see Responsibilities).
A platform MAY present expires_at in any time zone and MUST NOT
present a later deadline, for example by showing only its date, which reads as
the end of that date. Without expires_at, a platform presents the terms, for
example "30 days from delivery", and MUST NOT present a date it computed
from them as the deadline.
A business SHOULD state expires_at once it can resolve the deadline,
typically on an order after the anchor event. It MUST NOT state an
expires_at earlier than the end of the window its duration and anchor
describe; when covered quantities reach the anchor at different times, such as a
line delivered in two shipments, that is the earliest of their window ends, so a
return requested by then is within the window for every covered quantity. A
business scopes policies per line with applies_to for finer resolution.
On an order, policies record the terms that applied at checkout (see
Order). Stating expires_at resolves those terms;
it does not change them. A business adds expires_at to the governing return
policy and MAY split that policy into per-line copies with applies_to to
state a deadline for each line, but every copy carries the terms of the checkout
policy unchanged: its eligibility kind, duration and anchor,
supported_resolutions, methods, and fees.
For a 30-day window anchored on delivered and an item delivered on
2026-03-02, the business states "expires_at": "2026-04-01T18:00:00Z", and
the platform shows "Return by Apr 1, 6:00 PM UTC" or its local equivalent (see
Order after delivery).
When the window's length varies with a choice the buyer makes at return time,
such as the resolution, duration states the shortest of those periods, since a
guarantee field holds for every eligible return (see
Responsibilities), and description states the longer
ones. A condition the business requires, such as the item being unworn or
unopened, is an eligibility requirement, not a shorter window: the window
applies only to otherwise eligible returns (see Eligibility).
Businesses differ on whether the clock starts at purchase, fulfillment, or
delivery, so a business that states a window MUST provide both duration
and anchor, and the schema requires them. There is no default anchor, and a
platform MUST NOT assume one. A business states no time restriction with
an unlimited eligibility; when the window is not stated structurally, it
omits eligibility and conveys the terms in description.
anchor is an open vocabulary. The well-known values are:
| Anchor | The clock starts when |
|---|---|
purchased |
The order is placed. |
fulfilled |
The business completes its fulfillment step for the method: handing the items to a carrier, readying them for pickup, or making a digital item available. |
delivered |
The buyer receives the items, including collecting a pickup order or gaining access to a digital item. |
fulfilled and delivered differ in whose action starts the clock: the
business completing its step, or the buyer receiving the items. If its clock
starts at an event no well-known anchor names, a business MAY use its own
anchor value or an earlier well-known one, such as purchased. A platform that
does not recognize an anchor value presents the window from description, and
still presents expires_at when the business states it.
Non-returnable items¶
To state that its return policy does not accept returns of the covered items,
such as a final-sale item, a business MUST set eligibility.type to
not_accepted. Because return terms do not apply, a business MUST NOT carry
supported_resolutions, methods, or fees on a not_accepted policy; the
schema rejects the combination, so no precedence between fields of one policy is
ever needed. A business MUST NOT use an empty supported_resolutions or
methods array to signal non-returnability, and the schema requires at least
one entry in each; omit a field that is not stated structurally. A
not_accepted eligibility is the single, unambiguous signal a platform acts on.
A not_accepted eligibility describes the business's return policy only (see
Responsibilities).
When a business requires the buyer to be shown that an item is final sale, it
emits a messages[] warning with presentation: "disclosure" and code equal
to dev.ucp.shopping.policy.return, targeting the item. The disclosure pairs
with the governing return policy at that node, as defined in
Presenting policies.
Return fees¶
fees lists the charges a return can incur, each as a
Return Fee: type states what the charge is for, methods the
channels it applies to, and amount, basis_points, and max_amount how
much. A restocking fee deducts from the refund however the item comes back, so
it is an entry that applies to every channel; a channel's cost, such as return
shipping by mail, is an entry scoped to that channel. This lets a business
state, for example, "free return shipping with a 15% restocking fee".
methods is an open vocabulary of return channels. The well-known values are
in_store, by_mail, and kiosk; a business MAY use others, and a
platform that does not recognize one presents it from description.
type is a reverse-domain fee kind, and amount and max_amount are charged
per the kind's unit. The well-known kinds are:
type |
Charged per |
|---|---|
dev.ucp.shopping.fee.restocking |
One whole quantity_unit.unit returned, or one each when quantity_unit is absent, as price is (see Quantities and units). |
dev.ucp.shopping.fee.return_shipping |
Return shipment, so units returned together incur it once and a return split across shipments incurs it for each one. |
dev.ucp.shopping.fee.handling |
Return request. |
A vendor kind, such as com.example.fee.eco, is charged per return request and
carries display_text, which the schema requires for any kind other than the
three above. A vendor fee charged on another basis, such as per item, omits
amount and states its terms in display_text (for example, "Eco fee, $1 per
item"); such an entry states no maximum, so a platform presents its text and
never an understated cap.
How much a fee charges is stated by the pricing fields it carries:
| Fields | States |
|---|---|
amount of 1 or more |
Up to that flat charge. |
amount: 0 |
Nothing is charged. |
basis_points, optionally with max_amount |
Up to that percentage, and at most max_amount when present. |
Neither amount nor basis_points |
The fee is charged at its actual cost, and no maximum is stated; the schema requires display_text. |
An entry may omit type only with amount: 0; such an entry states that the
business charges no fee of any kind in its scope, including vendor kinds and
kinds defined later.
basis_points applies to the amount paid for the returned quantity, after
item-level discounts and excluding tax, shipping, and other fees; on catalog
surfaces, where nothing has been paid, it applies to the covered product's
price. An entry carries amount or basis_points but not both, and
max_amount only with basis_points; the schema rejects either combination.
An entry that lists methods applies only to those channels; an entry without
methods applies to every channel. When more than one entry applies to a fee on
a channel, an entry that lists the channel takes precedence over one that does
not; among entries that both list the channel or both do not, an entry with a
type takes precedence over one without. For example, { "methods":
["in_store"], "amount": 0 } makes in-store returns free even when another entry
charges a restocking fee on every channel. An entry may list a channel the
policy's methods does not.
A business MUST NOT carry two entries of the same type, or two entries
without type, that apply to the same channel. If it does, a platform treats
those fees as not stated for that channel.
max_amount caps a percentage. For items priced in USD,
{ "type": "dev.ucp.shopping.fee.restocking", "basis_points": 1500, "max_amount": 5000 }
means 15% of the amount paid, at most $50.00 per unit. For 3 units bought at
$200.00 each, the estimated maximum deduction is 3 × min($30.00, $50.00) =
$90.00.
Fees are estimated maximums. A business MUST NOT state an amount,
basis_points, or max_amount lower than its policy charges for that fee,
including a fee that applies only in some cases, such as a restocking fee on
opened items, which it states at its full value with display_text naming the
condition; a fee it cannot bound omits them. The business sets the charge for a
particular return when it processes the return, and that charge can differ from
an amount derived from these fields, for example with the item's condition,
return shipping rates, taxes, or how the order's discounts are refunded. A
platform MUST NOT present a fee, or an amount derived from it, as the final
charge for a particular return.
For example, a policy that charges a capped restocking fee, offers free returns in store and at kiosks, and charges return shipping and an eco fee by mail states:
[
{
"type": "dev.ucp.shopping.policy.return",
"description": {...},
"methods": ["in_store", "kiosk", "by_mail"],
"fees": [
{ "type": "dev.ucp.shopping.fee.restocking", "basis_points": 1500, "max_amount": 5000, "display_text": "15% restocking fee on opened items" },
{ "methods": ["in_store", "kiosk"], "amount": 0 },
{ "type": "dev.ucp.shopping.fee.return_shipping", "methods": ["by_mail"], "amount": 500 },
{ "type": "com.example.fee.eco", "methods": ["by_mail"], "amount": 100, "display_text": "Eco packaging fee" }
]
}
]
Per channel, its entries resolve to the following; a fee type with no entry for a channel is not stated:
| Channel | Resulting fees |
|---|---|
in_store, kiosk |
Free, since no fee of any kind is charged (the amount: 0 entry without type). |
by_mail |
Restocking: up to 15% of the amount paid, at most 5000 minor units per unit returned. Return shipping: up to 500 minor units per return shipment. com.example.fee.eco: up to 100 minor units per return request. |
Only amount: 0 states that nothing is charged. A platform MUST NOT treat
any other state as zero cost, including an omitted fees or a fee type with no
entry for a channel, whether it presents the terms, for example as "free" or "no
restocking fee", or estimates a return's charges (see
Responsibilities). A platform MAY present a channel as
free only when an entry without type covers it and no entry with type
resolves to a charge there.
Amounts (amount and max_amount on a fee) carry no currency of their own.
They are in the currency the covered items are priced in, which in a cart,
checkout, or order is its currency. A business MUST limit a policy that
carries an amount to items priced in a single currency. A catalog response can
price products in more than one currency, so a business scopes such a policy
with applies_to. A platform MUST NOT interpret an amount on a policy whose
covered items are priced in different currencies.
Targeting and precedence¶
Targeting and precedence are provided by the policies[] primitive and are not
redefined here. In short: a policy with no applies_to is the response-wide
default; a policy that targets specific items overrides it where they overlap,
with the narrowest same-type target winning. See
Targeting and
Precedence.
For return policies this means a business states a single default return policy
once, then adds targeted overrides only for the exceptions (a final-sale item, a
category with a different window), rather than repeating a policy on every line.
An override replaces the default for the items it governs and inherits none of
its terms, so a business MUST restate in the override every term that still
applies. A term the override omits, including fees, is not stated for the
items it governs.
Responsibilities¶
Return policies are business-stated facts. They are response-only data
(ucp_request: omit on cart, checkout, and order) that a platform never
submits; there is no buyer selection and no request-side machinery. They carry
no buyer-asserted claims and no PII, so they cross no new trust boundary beyond
the base response.
A return policy states the business's own terms. A business SHOULD localize
them to the buyer where it can, using what the request provides, such as the
buyer's context or a shipping address, so the terms are those that apply to
that buyer. The policy does not describe remedies the buyer may have under
applicable law or a third-party guarantee; those are outside this specification.
The return-term fields (eligibility, supported_resolutions, methods, and
fees) are not an exhaustive statement of the policy's terms. eligibility,
supported_resolutions, and methods are guarantees that hold for every
eligible return in the policy's scope: within a window eligibility, each
listed resolution is available through at least one listed method, and each
listed method accepts at least one listed resolution (see Window and
anchor). The lists do not state which resolutions each
method accepts; a platform MUST NOT present a specific resolution as
available through a specific method unless a single method or a single
resolution is listed. fees is a return term but not a guarantee: it states
estimated maximums (see Return fees). A business MAY
describe eligibility requirements, such as tags attached or original packaging,
in description (see Eligibility). A condition the business
requires of the item or return, such as unopened, tags attached, or proof of
purchase, is an eligibility requirement: the guarantee fields state the terms
for returns that meet it, and description states the requirement and any terms
for returns that don't. A choice the buyer makes at return time, such as the
resolution, is not a requirement; the guarantee fields state what holds for
every such choice.
A business SHOULD populate the fields it can state as guarantees and
MUST NOT populate a guarantee field with a value that does not hold for
every eligible return. It omits such a field when it cannot state the term as a
guarantee, and conveys the term in description or links to it through url.
fees follows Return fees instead. Where a term depends on
something known when the response is built, such as the seller, the purchase
date, an authenticated buyer's membership, or a selected payment instrument, a
business SHOULD resolve it, scoping policies with applies_to where it
differs per item.
A policy's description and its structured fields are two representations of
the same policy. The description MAY state better terms than a field (a
longer window for one resolution) or the conditions under which a fee applies,
but a business MUST NOT let it state terms worse for the buyer than a field
for the returns that field covers. It is the human-readable summary a platform
renders, and the fallback for a platform that does not model the type. A
platform SHOULD surface url alongside the structured terms so the buyer
can review the full policy.
A platform that models this type MAY present a guaranteed return deadline
from expires_at (see Window and anchor) and MAY
estimate a return's charges from fees (see Return fees). It
MUST NOT treat an elapsed window as making an item non-returnable, since the
description may allow later returns; only a not_accepted eligibility states
that its return policy accepts no returns.
Examples¶
Checkout with a default policy and a final-sale override¶
A response-wide return policy states the standard terms once. An item-scoped
not_accepted policy marks the engraved line (line 2) as final sale; because it
names that line with a singular query ($.line_items[2]), it governs it under
same-type precedence, outranking any Set match at the same depth, while every
other line keeps the default. A paired disclosure compels the final-sale notice
to be shown. The default policy's 15% restocking fee is an estimated maximum;
its display_text names the condition (opened items) under which it applies.
[
{
"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"
}
]
The paired disclosure that compels display of the final-sale term:
[
{
"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."
}
]
Catalog with a store-credit-only return policy¶
On a catalog surface, a return policy targets one product whose returns are
store credit only, a material purchase-decision factor. Store credit is the only
resolution the policy guarantees, and its description rules out any other:
[
{
"type": "dev.ucp.shopping.policy.return",
"description": { "plain": "Returns accepted within 30 days for store credit only." },
"applies_to": ["$.products[0]"],
"eligibility": { "type": "window", "duration": "P30D", "anchor": "purchased" },
"supported_resolutions": ["store_credit"]
}
]
Returns with no time limit¶
A business accepts returns of items with their tags attached at any time, for
store credit. An unlimited eligibility states that no time restriction
applies, and only that: the tags-attached requirement is stated in the
description, and the fields alone do not establish that a particular attempted
return meets it. Stating unlimited differs from omitting eligibility, which
says nothing structurally about time and leaves a platform to read any window
from the description:
[
{
"type": "dev.ucp.shopping.policy.return",
"description": { "plain": "Items with tags attached can be returned at any time for store credit." },
"eligibility": { "type": "unlimited" },
"supported_resolutions": ["store_credit"]
}
]
Order after delivery¶
Once the first line of an order has been delivered, the business resolves its
30-day window and states the deadline as expires_at, copying the checkout
policy's terms and scoping the copy to that line with applies_to so a line
delivered later carries its own deadline:
{
"ucp": {...},
"id": "order_abc123",
"checkout_id": "checkout_xyz789",
"permalink_url": "https://example.com/orders/abc123",
"currency": "USD",
"line_items": [
{
"id": "li_jacket",
"item": { "id": "prod_jacket", "title": "Trail Jacket", "price": 12000 },
"quantity": { "original": 1, "total": 1, "fulfilled": 1 },
"totals": [...],
"status": "fulfilled"
}
],
"fulfillment": {...},
"totals": [...],
"policies": [
{
"type": "dev.ucp.shopping.policy.return",
"description": { "plain": "30-day returns from delivery. Refund to original payment method." },
"applies_to": ["$.line_items[0]"],
"eligibility": {
"type": "window",
"duration": "P30D",
"anchor": "delivered",
"expires_at": "2026-04-01T18:00:00Z"
},
"supported_resolutions": ["original_payment_method"],
"url": "https://example.com/returns"
}
]
}