Promo codes and discounts implementation

This guide provides the technical API reference and payload schemas for handling promotional codes and discounts in version 2026-01-23 of the Universal Commerce Protocol (UCP).

Before building your endpoints, ensure you have reviewed the Promo codes and discounts overview for the high-level concepts, mathematical invariants, and error handling rules.

Discovery

To receive discount codes from Google, you must advertise discount support in your profile. In version 2026-01-23, the discount capability only extends checkout sessions.

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

Impact on line items and totals

Applied discounts are reflected in the core checkout fields using two distinct total types. If a discount has allocations pointing to line items, it contributes to items_discount. Discounts without allocations, or with allocations to shipping or fees, contribute to discount.

Discount type Total type Where reflected
Line-item discount items_discount line_items[].totals[type=items_discount]
Order-level discount discount totals[type=discount]

Version requirement:

For version 2026-01-23, discount amounts in totals[] and line_items[].totals[] are represented as positive integers, even though they represent a subtractive value to the receipt. Amounts inside the discounts.applied and allocations arrays also remain positive integers.

API examples for auto-applied promotions

The following examples demonstrate discounts applied automatically. The request payloads don't contain a discounts.codes array, but the responses include "automatic": true and omit the code field.

Auto-applied item-level discount

A 10% sitewide sale automatically applied to a specific line item.

Request example:

{
  "line_items": [
    {
      "id": "li_1",
      "item": { "title": "Sneakers", "price": 10000 },
      "quantity": 1
    }
  ]
}

Response example:

{
  "line_items": [
    {
      "id": "li_1",
      "item": { "title": "Sneakers", "price": 10000 },
      "quantity": 1,
      "totals": [
        {"type": "subtotal", "amount": 10000},
        {"type": "items_discount", "amount": 1000},
        {"type": "total", "amount": 9000}
      ]
    }
  ],
  "discounts": {
    "applied": [
      {
        "title": "10% Off Sitewide Sale",
        "amount": 1000,
        "automatic": true,
        "method": "each",
        "allocations": [
          {"path": "$.line_items[0]", "amount": 1000}
        ]
      }
    ]
  },
  "totals": [
    {"type": "subtotal", "display_text": "Subtotal", "amount": 10000},
    {"type": "items_discount", "display_text": "Item Discounts", "amount": 1000},
    {"type": "total", "display_text": "Total", "amount": 9000}
  ]
}

Auto-applied order-level discount

A promotional rule (e.g., "$10 off orders over $50") applied to the order as a whole without specific line-item allocations.

Request example:

{
  "line_items": [ ... ]
}

Response example:

{
  "line_items": [ ... ],
  "discounts": {
    "applied": [
      {
        "title": "$10 Off Orders Over $50",
        "amount": 1000,
        "automatic": true
      }
    ]
  },
  "totals": [
    {"type": "subtotal", "display_text": "Subtotal", "amount": 6000},
    {"type": "discount", "display_text": "Order Promo", "amount": 1000},
    {"type": "total", "display_text": "Total", "amount": 5000}
  ]
}

API examples for user-applied promotions

The following examples demonstrate discounts triggered by a user entering a promotional code. The request payloads include the requested codes, and the responses echo them back while allocating the applied amounts.

Order-level discount

A flat discount applied to the order total. No allocations are needed; the discount applies to the order as a whole and uses type: "discount".

Request example:

{
  "line_items": [ ... ],
  "discounts": {
    "codes": ["SAVE10"]
  }
}

Response example:

{
  "line_items": [ ... ],
  "discounts": {
    "codes": ["SAVE10"],
    "applied": [
      {
        "code": "SAVE10",
        "title": "$10 Off Your Order",
        "amount": 1000
      }
    ]
  },
  "totals": [
    {"type": "subtotal", "display_text": "Subtotal", "amount": 5000},
    {"type": "discount", "display_text": "Order Discount", "amount": 1000},
    {"type": "total", "display_text": "Total", "amount": 4000}
  ]
}

Mixed discounts (item + order level)

This example shows both discount types: a per-item discount (20% off) allocated to line items, and an automatic shipping discount at the order level.

Request example:

{
  "line_items": [ ... ],
  "discounts": {
    "codes": ["SUMMER20"]
  }
}

Response example:

{
  "line_items": [
    {
      "id": "li_1",
      "item": { "title": "T-Shirt", "price": 2000 },
      "quantity": 2,
      "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,
        "allocations": [
          {"path": "$.line_items[0]", "amount": 800}
        ]
      },
      {
        "title": "Free shipping on orders over $30",
        "amount": 599,
        "automatic": true
      }
    ]
  },
  "totals": [
    {"type": "subtotal", "display_text": "Subtotal", "amount": 4000},
    {"type": "items_discount", "display_text": "Item Discounts", "amount": 800},
    {"type": "discount", "display_text": "Order Discounts", "amount": 599},
    {"type": "fulfillment", "display_text": "Shipping", "amount": 0},
    {"type": "total", "display_text": "Total", "amount": 2601}
  ]
}

Rejected discount code

When a code is invalid, it is echoed in codes but omitted from applied. The rejection is communicated using a warning in the messages[] array.

Request example:

{
  "line_items": [ ... ],
  "discounts": {
    "codes": ["SAVE10", "EXPIRED50"]
  }
}

Response example:

{
  "line_items": [ ... ],
  "discounts": {
    "codes": ["SAVE10", "EXPIRED50"],
    "applied": [
      {
        "code": "SAVE10",
        "title": "$10 Off Your Order",
        "amount": 1000
      }
    ]
  },
  "totals": [
    {"type": "subtotal", "display_text": "Subtotal", "amount": 5000},
    {"type": "discount", "display_text": "Order Discount", "amount": 1000},
    {"type": "total", "display_text": "Total", "amount": 4000}
  ],
  "messages": [
    {
      "type": "warning",
      "code": "discount_code_expired",
      "path": "$.discounts.codes[1]",
      "content": "Code 'EXPIRED50' expired on December 1st"
    }
  ]
}

Stacked discounts with allocations

Multiple discounts applied with full allocation breakdowns.

Request example:

{
  "line_items": [ ... ],
  "discounts": {
    "codes": ["SUMMER20", "EXTRA5"]
  }
}

Response example:

{
  "line_items": [
    {
      "id": "li_1",
      "item": { "title": "T-Shirt", "price": 6000 },
      "quantity": 1,
      "totals": [
        {"type": "subtotal", "amount": 6000},
        {"type": "items_discount", "amount": 1500},
        {"type": "total", "amount": 4500}
      ]
    },
    {
      "id": "li_2",
      "item": { "title": "Socks", "price": 4000 },
      "quantity": 1,
      "totals": [
        {"type": "subtotal", "amount": 4000},
        {"type": "items_discount", "amount": 1000},
        {"type": "total", "amount": 3000}
      ]
    }
  ],
  "discounts": {
    "codes": ["SUMMER20", "EXTRA5"],
    "applied": [
      {
        "code": "SUMMER20",
        "title": "Summer Sale 20% Off",
        "amount": 2000,
        "method": "each",
        "priority": 1,
        "allocations": [
          {"path": "$.line_items[0]", "amount": 1200},
          {"path": "$.line_items[1]", "amount": 800}
        ]
      },
      {
        "code": "EXTRA5",
        "title": "Extra $5 Off",
        "amount": 500,
        "method": "across",
        "priority": 2,
        "allocations": [
          {"path": "$.line_items[0]", "amount": 300},
          {"path": "$.line_items[1]", "amount": 200}
        ]
      }
    ]
  },
  "totals": [
    {"type": "subtotal", "display_text": "Subtotal", "amount": 10000},
    {"type": "items_discount", "display_text": "Item Discounts", "amount": 2500},
    {"type": "total", "display_text": "Total", "amount": 7500}
  ]
}