---
title: Discounts
slug: discounts
abstract: Provide configurable percentage-based pricing adjustments with quantity tiers at the catalog, category, or product level.
content_type: concept
audience: \[developer, architect]
---
# Discounts

Discounts let you apply percentage-based price reductions with quantity tiers at the catalog, category, or product level. They support party-based assignments to buyer groups, buyers, and user groups.

When multiple discounts match the same item, OrderCloud applies the discount that gives the lowest price. Discounts do not stack.

> [!notice]
> **Beta feature**
> This feature is currently in beta. Behavior and properties might change before general availability.

## Discount model

```json
{
	"ID": "",
	"Description": "",
	"DiscountBreaks": [
		{
			"Quantity": 10,
			"Amount": 10.0
		},
		{
			"Quantity": 20,
			"Amount": 15.0
		}
	],
	"CatalogID": "",
	"CategoryID": "",
	"ProductID": "",
	"ProductFilter": "",
	"xp": {}
}
```

### Discount properties

| Property | Type | Description | Required |
| ---------- | ---------- | ---------- | ---------- |
| `ID` | string | Unique identifier. | Yes |
| `Description` | string | Optional description. Maximum length is 2000 characters. | No |
| `DiscountBreaks` | array | Quantity tiers for discount percentages. | Yes |
| `CatalogID` | string | Limits the discount to a catalog. | No |
| `CategoryID` | string | Limits the discount to a category. | No |
| `ProductID` | string | Limits the discount to a product. | No |
| `ProductFilter` | string | Limits the discount to products that match a filter expression. | No |
| `xp` | object | Extended properties. | No |

If CatalogID, CategoryID, and ProductID are all `null`, the discount is applied globally across all products available to the assigned party or parties.

Use ProductFilter to target products with specific values on an xp key. For example, create a discount for products where `xp.color=red`.

### DiscountBreak properties

| Property | Type | Description | Required |
| ---------- | ---------- | ---------- | ---------- |
| `Quantity` | int | Minimum quantity for this break. | Yes |
| `Amount` | decimal | Discount percentage greater than 0 and less than or equal to 100. | Yes |

For each discount, OrderCloud selects the highest break where `Quantity <= LineItem.Quantity`.

## Assignment model

```json
{
	"DiscountID": "",
	"BuyerGroupID": "",
	"BuyerID": "",
	"UserGroupID": ""
}
```

### Assignment properties

| Property | Type | Description | Required |
| ---------- | ---------- | ---------- | ---------- |
| `DiscountID` | string | Discount to assign. | Yes |
| `BuyerGroupID` | string | Buyer group to assign. | Conditionally |
| `BuyerID` | string | Buyer to assign. | Conditionally |
| `UserGroupID` | string | User group to assign. | Conditionally |

### Assignment rules

Use one of these valid combinations:

1. Buyer group assignment: `BuyerGroupID`
2. Buyer assignment: `BuyerID`
3. User group assignment: `BuyerID` and `UserGroupID`

## API endpoints

### Discount management

| Operation | Endpoint |
| ---------- | ---------- |
| List discounts | `GET /v1/discounts` |
| Get discount | `GET /v1/discounts/{discountID}` |
| Create discount | `POST /v1/discounts` |
| Replace discount | `PUT /v1/discounts/{discountID}` |
| Update discount | `PATCH /v1/discounts/{discountID}` |
| Delete discount | `DELETE /v1/discounts/{discountID}` |

### Assignment management

| Operation | Endpoint |
| ---------- | ---------- |
| List assignments | `GET /v1/discounts/assignments` |
| Create assignment | `POST /v1/discounts/assignments` |
| Delete assignment | `DELETE /v1/discounts/{discountID}/assignments` |

When you call `DELETE /v1/discounts/{discountID}/assignments`, pass the target party in query parameters:

1. `buyerGroupID` for a buyer group assignment
2. `buyerID` for a buyer assignment
3. `buyerID` and `userGroupID` for a user group assignment

## Authorization

| Role | Access |
| ---------- | ---------- |
| `PriceScheduleAdmin` | Full CRUD access to discounts and assignments |
| `PriceScheduleReader` | Read-only access to discounts and assignments |

## Create and assign discounts

### Create a volume discount

`POST /v1/discounts`

```json
{
	"ID": "enterprise-volume",
	"Description": "Enterprise customer volume pricing",
	"DiscountBreaks": [
		{ "Quantity": 1, "Amount": 10.0 },
		{ "Quantity": 50, "Amount": 15.0 },
		{ "Quantity": 100, "Amount": 20.0 }
	],
	"CatalogID": "industrial-equipment"
}
```

### Assign to a buyer group

`POST /v1/discounts/assignments`

```json
{
	"DiscountID": "enterprise-volume",
	"BuyerGroupID": "enterprise-customers"
}
```

## Buyer-side discount data

When a buyer requests products through `GET /v1/me/products`, OrderCloud evaluates applicable assignments and returns the selected discount in the product pricing payload.

```json
{
  "ID": "product-123",
  "Name": "Industrial Widget",
  "PriceSchedule": {
    "ID": "standard-pricing",
    "Name": "Standard Price Schedule",
    "Discount": {
      "ID": "enterprise-volume",
      "Description": "Enterprise customer volume pricing"
    },
    "PriceBreaks": [
      {
        "Quantity": 1,
        "Price": 100.00,
        "SalePrice": null,
        "Discounted": {
          "Price": 90.00,
          "SalePrice": null,
          "Percent": 10.0
        }
      },
      {
        "Quantity": 50,
        "Price": 100.00,
        "SalePrice": null,
        "Discounted": {
          "Price": 85.00,
          "SalePrice": null,
          "Percent": 15.0
        }
      }
    ]
  }
}
```

### Selection logic

1. OrderCloud evaluates discounts assigned to the buyer group, buyer, and user group.
2. If multiple discounts match, OrderCloud picks the discount that returns the lowest price.
3. For the selected discount, OrderCloud picks the highest matching break where `Quantity <= LineItem.Quantity`.
4. The ID of the selected discount is available in `PriceSchedule.Discount.ID`; `PriceSchedule.Discount.Description` is also available for display purposes.
5. If no discount matches, `PriceBreak.Discounted` is `null`.

### Derived Price Breaks

When a discount's quantity tiers don't align with the price schedule's quantity tiers, the buyer response includes **derived price breaks** at the discount-break quantities. This ensures buyers always see distinct pricing for every meaningful quantity threshold, without requiring sellers to duplicate price break definitions.

**Example:** A price schedule has one price break at qty 1 ($100.00). A discount applies 10% at qty 1 and 15% at qty 20. The buyer sees two price breaks:

| Quantity | Price | Discounted.Price | Discounted.Percent |
| ---------- | ---------- | ---------- | ---------- |
| 1 | $100.00 | $90.00 | 10 |
| 20 | $100.00 | $85.00 | 15 |

The qty-20 break is derived - its base `Price` is inherited from the applicable price tier (qty 1). The number of `PriceBreaks` on `BuyerPriceSchedule` equals the number of **distinct** quantity values across all price breaks and discount breaks.

#### Constraints on Derived Breaks

A discount-break quantity is **not** derived as an additional price break if any of the following apply:

- **`RestrictedQuantity` is `true`** - buyers can only order at exact price break quantities, so intermediate breaks are meaningless. No derivation occurs at all; the discount is still applied to matching price break quantities.
- **Quantity is below `MinQuantity`** - the schedule prohibits ordering at that quantity.
- **Quantity is above `MaxQuantity`** - the schedule prohibits ordering at that quantity.
- **No applicable price break tier exists** - the discount-break quantity falls below the lowest price break quantity (no base price can be inherited).

If the above constraints eliminate all discount coverage across every price break, `PriceSchedule.Discount` is set to `null`.

## Discount persistence on line items and orders

When a line item is created or updated, OrderCloud calculates the discount and stores the result on the line item and order.

### Line item fields

| Property | Type | Description |
| ---------- | ---------- | ---------- |
| `DiscountID` | string | ID of the applied discount. Read-only. |
| `BaseDiscount` | decimal | Discount amount for the line item in currency units. Read-only. |

`BaseDiscount` uses this calculation:

`LineSubtotal * (DiscountPercentage / 100)`

### Order field

| Property | Type | Description |
| ---------- | ---------- | ---------- |
| `BaseDiscount` | decimal | Sum of all `LineItem.BaseDiscount` values. Read-only. |

### Persistence example

```json
{
	"ID": "ORDER-123",
	"Subtotal": 200.0,
	"BaseDiscount": 40.0,
	"Total": 160.0,
	"LineItems": [
		{
			"ID": "LI-001",
			"ProductID": "product-123",
			"Quantity": 2,
			"UnitPrice": 100.0,
			"LineSubtotal": 200.0,
			"DiscountID": "enterprise-volume",
			"BaseDiscount": 40.0,
			"LineTotal": 160.0
		}
	]
}
```

## Related reading

- [Multiple prices](https://doc.sitecore.com/ordercloud/en/developers/platform/multiple-prices.html)
- [Sale prices](https://doc.sitecore.com/ordercloud/en/developers/platform/sale-prices.html)


