Skip to content

Conversation

@igrigorik
Copy link
Contributor

@igrigorik igrigorik commented Jan 17, 2026

Summary

Cart capability (dev.ucp.shopping.cart) enables pre-purchase exploration without checkout complexity. Checkout models buying — payment handlers, status lifecycle, order finalization. Cart models shopping — browsing, comparing, saving items for later, all without payment commitment.

Motivation

Platforms need a lightweight way to build baskets before purchase intent is established — session persistence, sharing, and exploratory workflows without the checkout contract. Checkout operations are expensive for businesses (payment handler negotiations, 3P calls, tax calculations with full address resolution, etc), and often subject to strict rate limits. Carts address this via lower-fidelity policies that accommodate efficient pre-purchase basket building and ability to service much higher volumes.

Goals

  • Enable efficient pre-purchase basket building with accurate estimates
  • Provide cart-to-checkout conversion via cart_id parameter
  • Maintain schema consistency with checkout (reuse line_item, buyer, context, totals)

Non-Goals

  • Payment configuration (checkout concern)
  • Status lifecycle management (cart is binary: exists/404)
  • Fulfillment, discounting, etc (addressed via capability extensions)
  • Subscriptions, loyalty, B2B pricing (ditto, capability extensions)

Detailed Design

Key differences from Checkout:

Aspect Cart Checkout
Purpose Pre-purchase exploration Purchase finalization
Payment None Required
Status Binary (exists/404) Lifecycle (incomplete → completed)
Totals Estimates Final pricing
Handoff URL checkout_url continue_url

API Operations:

Operation REST MCP
Create POST /carts create_cart
Get GET /carts/{id} get_cart
Update PUT /carts/{id} update_cart
Cancel POST /carts/{id}/cancel cancel_cart

Cart-to-Checkout Conversion:

When checkout is initialized via cart_id, sessions SHOULD be linked for checkout duration. Business SHOULD maintain cart for back-to-storefront flows. After checkout completion, business MAY clear cart.

Schema:

  • Cart reuses checkout entities: line_item, buyer, context, totals, messages, links
  • New cart_id field on checkout (via schema extension) for conversion
  • response_cart added to ucp.json for cart response metadata

Update Semantics: Full replacement (mirrors checkout)
Cancel Behavior: Business MUST return cart state before deletion. Subsequent operations SHOULD return 404.
Tax Guidance: Taxes MAY be included where calculable. Platforms SHOULD assume cart totals are estimates when provided.

Risks and Mitigations

  • Complexity: Cart adds another capability to implement. Mitigation: Cart reuses checkout schemas — no new entity types. Simpler than checkout (no payment, no status lifecycle).
  • Backward Compatibility: None — new capability, opt-in via profile advertisement.
  • Security: Cart stores buyer signals (context, optional buyer info). Mitigation: Same security model as checkout. TTL via expires_at. Explicit cancel_cart for privacy.
  • Schema Drift: Cart and checkout share entities. Mitigation: Cart explicitly references checkout schemas ($ref), ensuring consistency.

  • New feature (non-breaking change which adds functionality)
  • Documentation update

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation

  Currency as client input is flawed - merchants control accepted
  currencies, not buyers. Allowing "show me price in $X" implies forex
  operations that break at payment time when the merchant's actual accepted
  currency differs.

  Correct model: buyer provides context signals, merchant determines currency
  and other market-relevant attributes such as product availability,
  shipping and delivery times, price, etc.

  Changes:
  - Add extensible Context type with country, region, postal_code for
    progressive disclosure
  - Add context to checkout (optional on create/update, omit on complete)
  - Make currency output-only (omit on all operations)

  This primitive will be relevant for catalog, cart, and checkout.
  Doc generator previously required every $ref property to duplicate the
  referenced type's description. This violated DRY - same description
  copied everywhere, diverging over time.

  New behavior:
  - Embedder provides description → use it (custom/contextual)
  - Embedder omits description → inherit from ref'd type

  JSON Schema allows omitting description, so embedders can now choose:
  override with context-specific text, or inherit the canonical definition.
Checkout models buying: payment handlers, status lifecycle, order
finalization. Cart models shopping: browsing, comparing, saving items
for later, all without payment commitment.

Key differences from Checkout:
- No payment configuration required
- Binary state (exists/404) vs lifecycle status
- Estimated totals vs final pricing
- checkout_url for sharing/handoff vs continue_url

The typical flow: cart session → checkout session → order

Includes:
- Cart schema (dev.ucp.shopping.cart) with CRUD operations
- REST binding: POST/GET/PUT /carts, POST /carts/{id}/cancel
- MCP binding: create_cart, get_cart, update_cart, cancel_cart tools
- Cart-to-checkout conversion via cart_id parameter
- Full input/output schemas with request payload wrappers
@igrigorik igrigorik added this to the Working Draft milestone Jan 17, 2026
@igrigorik igrigorik self-assigned this Jan 17, 2026
@igrigorik igrigorik added the TC review Ready for TC review label Jan 17, 2026
order finalization, cart provides a lightweight CRUD interface for item
collection before purchase intent is established.

**When to use Cart vs Checkout:**

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m having trouble understanding why we need Cart capability here. It seems more like a Checkout in draft status and make payment (it is actually already supporting empty object) optional during the Checkout creation?

Copy link

@raginpirate raginpirate left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing how such a complex primitive like cart is suddenly "simple" because it inherits all of these build blocks from checkout 🔥

I added a few small nits, I'm really interested in having a dialogue about payments <-> cart, I'm thinking its worth keeping handlers around. Also interested in seeing fulfillment extend cart!


* **MUST** provide `checkout_url` in all cart responses.
* **SHOULD** provide estimated totals when calculable.
* **MAY** omit shipping totals until checkout when address is unknown.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* **MAY** omit shipping totals until checkout when address is unknown.
* **MAY** omit fulfillment totals until checkout when address is unknown.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has opened a series of questions for me:

  • Looks like we need changes to the fulfillment capability to extend cart 🤔
  • or... would we express this as an extension on the base fulfillment capability and cart capability simultaneously?
  • And if so... how do we express the dependency graph? At the moment we allow one parent <-> child. Do we care to express this?

* **SHOULD** follow [cart lifecycle requirements](#cart-to-checkout-conversion)
when checkout is initialized via `cart_id`.

## Capability Schema Definition

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
## Capability Schema Definition
## Cart Schema Definition

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this title I was thinking I was going to be reading the config schema, so just adding some clarity to wording.

| Aspect | Cart | Checkout |
|--------|------|----------|
| **Purpose** | Pre-purchase exploration | Purchase finalization |
| **Payment** | None | Required (handlers, instruments) |

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Challenge: if we're ok with fulfillment extending cart to build fulfillment estimates, shouldn't we be ok with payment handlers being negotiated and pruned down based on cart shape, to assist with accelerated checkouts from cart?

I'm fine without instruments though, we don't need store credit / vaulted cards surfacing through cart.

Comment on lines +154 to +157
## Entities

Cart reuses the same entity schemas as [Checkout](checkout.md). This ensures
consistent data structures when converting a cart to a checkout session.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This description is good, but adding a table in this page for every existing checkout entity feels like a lot of repetition and makes me feel like maybe there is going to be a discrepancy in the descriptions or something.

My proposal: turn this into a simple list:

  • line items
  • buyer
  • context
  • total
  • message

With links to the checkout page for each.


## Protocol Fundamentals

### Base URL
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I put up a PR this morning to add a Discovery section on the Checkout REST docs, matching the equivalent section in the MCP docs: https://github.com/Universal-Commerce-Protocol/ucp/pull/82/changes. I think the same change would be useful here, as the same problem (the ucp.dev schema URL for REST is not provided anywhere on the page) applies to this new Cart REST page.

"description": "Optional merchant links (policies, FAQs).",
"ucp_request": "omit"
},
"checkout_url": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you consider calling this continue_url for symmetry with Checkout? I personally find this name a little confusing, since the API endpoint is really the "checkout URL" for many UCP implementers.

}
},
"type": "object",
"required": ["ucp", "id", "line_items", "currency", "totals", "checkout_url"],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

continue_url is optional on Checkout. Is there a reason checkout_url needs to be mandatory for Carts? I assumed it covered similar "human in the loop" flows, and would therefore have the same optionality in the schema as continue_url.

* **During active checkout** — Business SHOULD maintain the cart and reflect
relevant checkout modifications (quantity changes, item removals) back to
the cart. This supports back-to-storefront flows when buyers transition
between checkout and storefront.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What should a business do if multiple checkouts are created from the same cart?

}
}
},
"/carts/{id}/cancel": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you consider making this a DELETE instead, since there is no Checkout-style status=cancelled, and since we seem to be trying to express "it exists or it doesn’t" semantics for Carts? There are a few places where we have to say something like, "doesn’t exist, has expired, or was cancelled" to describe the 404 case, which I assume could also be simplified a bit if we treated this as a DELETE.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

TC review Ready for TC review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants