Skip to content

Discount Extension

Discount extension allows businesses to indicate that they support discount codes on cart and checkout sessions, and specifies how the discount codes are to be shared between the platform and the business.

Key features:

  • Submit one or more discount codes
  • Receive applied discounts with human-readable titles and amounts
  • Rejected codes communicated via messages[] with detailed error codes
  • Automatic discounts surfaced alongside code-based discounts

Dependencies:

  • Cart Capability or Checkout Capability

Businesses advertise discount support in their profile. The capability can extend cart, checkout, or both:

{
"ucp": {
"version": "2026-01-11",
"capabilities": {
"dev.ucp.shopping.discount": [
{
"version": "2026-01-11",
"extends": ["dev.ucp.shopping.cart", "dev.ucp.shopping.checkout"],
"spec": "https://ucp.dev/2026-01-11/specification/discount",
"schema": "https://ucp.dev/2026-01-11/schemas/shopping/discount.json"
}
]
}
}
}

Businesses MAY advertise discount support for cart only, checkout only, or both. Platforms SHOULD check which resources are extended before submitting discount codes.

When this capability is active, cart and/or checkout are extended with a discounts object.

FieldTypeDescription
codesarrayDiscount codes submitted by the platform
appliedarrayApplied discounts (code-based + automatic)
FieldTypeDescription
codestringThe discount code (absent for automatic discounts)
titlestringHuman-readable discount name
amountintegerDiscount amount in minor currency units
methodstringCalculation method: each or across
priorityintegerApplication order (lower = applied first)
automaticbooleanTrue for automatically applied discounts
allocationsarrayBreakdown of where discount was applied
FieldTypeDescription
pathstringJSONPath targeting the discounted resource
amountintegerAmount allocated to this target (minor currency units)

The applied array explains how discounts were calculated and distributed.

The method field indicates how the discount was calculated:

MethodMeaningExample
eachApplied independently per eligible item”10% off each item” → 10% × item price
acrossSplit proportionally by value”$10 off order” → $6 to $60 item, $4 to $40 item

When multiple discounts are applied, priority indicates the calculation order. Lower numbers are applied first:

Cart: $100
Discount A (priority: 1): 20% off → $100 × 0.8 = $80
Discount B (priority: 2): $10 off → $80 - $10 = $70

The allocations array breaks down where each discount dollar landed, using JSONPath to identify targets:

Path PatternTarget
$.line_items[0]First line item
$.line_items[1]Second line item
$.totals.shippingShipping cost

Invariant: Sum of allocations[].amount equals applied_discount.amount.

Discount codes are submitted via standard cart or checkout create/update operations. The same semantics apply to both resources.

Request behavior:

  • Replacement semantics: Submitting discounts.codes replaces any previously submitted codes
  • Clear codes: Send empty array "codes": [] to remove all discount codes
  • Case-insensitive: Codes are matched case-insensitively by business

Response behavior:

  • discounts.applied contains all active discounts (code-based + automatic)
  • Rejected codes communicated via messages[] (see below)
  • Discount amounts reflected in totals[] and line_items[].discount

Cart-to-checkout continuity: When a cart is converted to a checkout via the cart capability’s cart_id field, businesses MUST carry forward any discount codes that were applied to the cart. Codes that are no longer valid at checkout time (e.g., expired, ineligible) SHOULD be communicated via messages[] using standard rejection codes.

When a submitted discount code cannot be applied, businesses communicate this via the messages[] array:

{
"messages": [
{
"type": "warning",
"code": "discount_code_expired",
"path": "$.discounts.codes[0]",
"content": "Code 'SUMMER20' expired on December 1st"
}
]
}

Implementation guidance: Operations that affect order totals, or the user’s expectation of the total, SHOULD use type: "warning" to ensure they are surfaced to the user rather than silently handled by platforms. Rejected discounts are a prime example—the user expects a discount but won’t receive it, so they should be informed.

Error codes for rejected discounts:

CodeDescription
discount_code_expiredCode has expired
discount_code_invalidCode not found or malformed
discount_code_already_appliedCode is already applied
discount_code_combination_disallowedCannot combine with another active discount
discount_code_user_not_logged_inCode requires authenticated user
discount_code_user_ineligibleUser does not meet eligibility criteria

Businesses may apply discounts automatically based on cart contents, customer segment, or promotional rules:

  • Appear in discounts.applied with automatic: true and no code field
  • Applied without platform action
  • Cannot be removed by the platform
  • Surfaced for transparency (platform can explain to user why discount was applied)

Applied discounts are reflected in the core cart or checkout fields using two distinct total types:

Total TypeWhen to Use
items_discountDiscounts allocated to line items ($.line_items[*])
discountOrder-level discounts (shipping, fees, flat order amount)

Determining the type: If a discount has allocations pointing to line items, it contributes to items_discount. Discounts without allocations, or with allocations to shipping/fees, contribute to discount.

Amount convention: All discount amounts are positive integers in minor currency units. When presenting totals to users, display discount types as subtractive (e.g., ”-$13.99”).

=== “Request”

```json
{
"line_items": [
{
"item": {
"id": "prod_1",
"quantity": 2,
"title": "T-Shirt",
"price": 2000
}
}
],
"discounts": {
"codes": ["SUMMER20"]
}
}
```

=== “Response”

```json
{
"id": "cart_abc123",
"line_items": [
{
"id": "li_1",
"totals": [
{"type": "subtotal", "amount": 4000},
{"type": "items_discount", "amount": 800},
{"type": "total", "amount": 3200}
]
}
],
"discounts": {
"codes": ["SUMMER20"],
"applied": [
{
"code": "SUMMER20",
"title": "Summer Sale 20% Off",
"amount": 800,
"method": "each",
"allocations": [
{"path": "$.line_items[0]", "amount": 800}
]
}
]
},
"totals": [
{"type": "subtotal", "display_text": "Subtotal", "amount": 4000},
{"type": "items_discount", "display_text": "Item Discounts", "amount": 800},
{"type": "total", "display_text": "Estimated Total", "amount": 3200}
]
}
```

When a discount code cannot be applied, the rejection is communicated via the messages[] array. The code still appears in discounts.codes (echoed back) but not in discounts.applied.

=== “Response”

```json
{
"discounts": {
"codes": ["SAVE10", "EXPIRED50"],
"applied": [
{
"code": "SAVE10",
"title": "$10 Off Your Order",
"amount": 1000
}
]
},
"messages": [
{
"type": "warning",
"code": "discount_code_expired",
"path": "$.discounts.codes[1]",
"content": "Code 'EXPIRED50' expired on December 1st"
}
]
}
```