---
title: Bulk updates for pricing and discounts
slug: bulk-updates-pricing-discounts
abstract: Use bulk endpoints to create, update, and delete price schedules, discounts, and discount assignments with asynchronous jobs.
content_type: concept
audience: \[developer, architect]
---
# Bulk updates

Bulk update endpoints let you process large pricing and discount workloads in fewer API calls. OrderCloud accepts each request, creates a bulk job, and processes items asynchronously. Currently the only supported resources are price schedules, discounts, and discount assignments, all of which are covered in this document. Additional resources might be added in the future.

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

## Bulk endpoints

| Resource | Operation | Endpoint | Description |
| ---------- | ---------- | ---------- | ---------- |
| Price schedules | Upsert | `POST /v1/bulk/priceschedules/upsert` | Create or update price schedules in bulk. |
| Price schedules | Delete | `POST /v1/bulk/priceschedules/delete` | Delete price schedules in bulk by ID. |
| Discounts | Upsert | `POST /v1/bulk/discounts/upsert` | Create or update discounts in bulk. |
| Discounts | Delete | `POST /v1/bulk/discounts/delete` | Delete discounts in bulk by ID. |
| Discount assignments | Upsert | `POST /v1/bulk/discounts/assignments/upsert` | Create or update discount assignments in bulk. |
| Discount assignments | Delete | `POST /v1/bulk/discounts/assignments/delete` | Delete discount assignments in bulk by composite key (DiscountID + BuyerID or BuyerGroupID). |

All endpoints return `202 Accepted` with job metadata.

## Common bulk behavior

### Asynchronous job processing

Each bulk request returns an initial job descriptor.

```json
{
  "JobID": "bulk_01J123ABC...",
  "Resource": "Discounts",
  "Operation": "Upsert",
  "Status": "Queued",
  "ItemsReceived": 50000,
  "ItemsCompleted": 0,
  "ItemsSucceeded": 0,
  "ItemsFailed": 0,
  "DateReceived": "2025-11-11T20:32:00Z",
  "DateStarted": null,
  "DateCompleted": null
}
```

To track progress, call:

```http
GET /v1/bulk/jobs/{jobID}
```

Example response:

```json
{
  "JobID": "bulk_01J123ABC...",
  "Resource": "Discounts",
  "Operation": "Upsert",
  "Status": "Queued | Running | Completed | Failed | Cancelled",
  "ItemsReceived": 50000,
  "ItemsCompleted": 48045,
  "ItemsSucceeded": 48000,
  "ItemsFailed": 45,
  "DateReceived": "2025-11-11T20:32:00Z",
  "DateStarted": "2025-11-11T20:32:11Z",
  "DateCompleted": null
}
```

Jobs remain queryable for 24 hours after completion.


### Error retrieval

To retrieve indexed item errors, call:

```http
GET /v1/bulk/jobs/{jobID}/errors
```

If no items fail, the response is an empty array:

```json
[]
```

Example error payload:

```json
[
  {
    "ItemIndex": 2341,
    "ErrorCode": "NotFound",
    "Message": "Catalog not found.",
    "Data": {
      "ObjectType": "Catalog",
      "ObjectID": "invalid-catalog-id"
    }
  }
]
```

Each error object includes:

- `ItemIndex`: Zero-based index from the original request.
- `ErrorCode`: Standardized error code.
- `Message`: Human-readable error message.
- `Data`: Optional context for the failure.

### Limits

| Limit | Value |
| ---------- | ---------- |
| Maximum items per request | 50,000 |
| Maximum payload size | 25 MB |
| Concurrent running jobs | 2 |

If payload size exceeds the limit, the API returns `400` with `Bulk.PayloadTooLarge`.

You can submit more than two jobs, but only two jobs run at the same time per marketplace. Additional jobs remain queued.

> [!tip]
> Use gzip compression for large payloads to reduce request size and upload time.

## Price schedule bulk operations

### Upsert price schedules

```http
POST /v1/bulk/priceschedules/upsert
```

Each item is a full price schedule object.

```json
{
  "Items": [
    {
      "ID": "B2B-DEFAULT-USD",
      "Name": "Default B2B Pricing - USD",
      "Currency": "USD",
      "MinQuantity": 1,
      "MaxQuantity": null,
      "UseCumulativeQuantity": false,
      "RestrictedQuantity": false,
      "ApplyTax": true,
      "ApplyShipping": true,
      "OrderType": "Standard",
      "PriceBreaks": [
        { "Quantity": 1, "Price": 10.0 },
        { "Quantity": 100, "Price": 9.5 }
      ],
      "SaleStart": "2025-11-01T00:00:00Z",
      "SaleEnd": "2025-11-30T23:59:59Z",
      "xp": {
        "Region": "NA"
      },
      "DefaultForProductID": "MY-PRODUCT",
      "DefaultForSupplierID": null
    }
  ]
}
```

Behavior:

1. If the ID does not exist, OrderCloud creates the price schedule.
2. If the ID exists, OrderCloud updates the price schedule (PUT-style replacement).
3. OrderCloud replaces the full `PriceBreaks` array.
4. If provided, OrderCloud applies default assignment fields after save.

### Default assignment fields for price schedules

Use `DefaultForProductID` and `DefaultForSupplierID` to set defaults during upsert.

| Combination | Result |
| ---------- | ---------- |
| `DefaultForProductID` only | Sets `Product.DefaultPriceScheduleID`. |
| `DefaultForProductID` and `DefaultForSupplierID` | Sets `ProductSupplier.DefaultPriceScheduleID` for that supplier on that product. |
| `DefaultForSupplierID` only | Invalid. Returns a validation error. |
| Neither field | Standard upsert without default assignment. |

Validation and ownership rules:

1. Referenced product and supplier objects must exist, or the item returns `NotFound`.
2. For supplier-owned schedules, ownership must match the supplier or marketplace owner, or the item returns `InvalidDefaultPriceScheduleID`.
3. If the default is already set, OrderCloud skips the write.
4. These fields do not change schedule ownership.

### Delete price schedules

```http
POST /v1/bulk/priceschedules/delete
```

```json
{
  "IDs": [
    "B2B-DEFAULT-USD",
    "SUMMER-PROMO-2024",
    "LEGACY-TEST-SCHEDULE"
  ]
}
```

Behavior:

1. OrderCloud deletes each ID independently.
2. Missing IDs return `NotFound` for those items.

## Discount bulk operations

### Upsert discounts

```http
POST /v1/bulk/discounts/upsert
```

Each item is a full discount object.

```json
{
  "Items": [
    {
      "ID": "ACME-CONTRACT-2024",
      "Description": "Acme Corp negotiated pricing",
      "DiscountBreaks": [
        { "Quantity": 1, "Amount": 18.0 },
        { "Quantity": 100, "Amount": 22.0 }
      ],
      "CategoryID": "heavy-machinery"
    },
    {
      "ID": "GLOBAL-MFG-VOLUME",
      "Description": "Global Manufacturing volume discount",
      "DiscountBreaks": [
        { "Quantity": 1, "Amount": 15.0 }
      ],
      "CatalogID": "industrial-equipment"
    }
  ]
}
```

Behavior:

1. If the ID does not exist, OrderCloud creates the discount.
2. If the ID exists, OrderCloud updates the discount (PUT-style replacement).
3. OrderCloud validates all break and scope rules per item.

### Delete discounts

```http
POST /v1/bulk/discounts/delete
```

```json
{
  "IDs": [
    "ACME-CONTRACT-2023",
    "LEGACY-VOLUME-2022",
    "EXPIRED-CONTRACT-Q4"
  ]
}
```

Behavior:

1. OrderCloud deletes each ID independently.
2. Missing IDs return `NotFound` for those items.

## Discount assignment bulk operations

### Upsert discount assignments

```http
POST /v1/bulk/discounts/assignments/upsert
```

```json
{
  "Items": [
    { "DiscountID": "ENTERPRISE-VOLUME", "BuyerGroupID": "enterprise-customers" },
    { "DiscountID": "ACME-CONTRACT-2024", "BuyerID": "acme-corp" },
    { "DiscountID": "GLOBAL-MFG-VOLUME", "BuyerID": "global-manufacturing", "UserGroupID": "procurement-team" }
  ]
}
```

Each item must follow discount assignment rules.

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

### Delete discount assignments

```http
POST /v1/bulk/discounts/assignments/delete
```

```json
{
  "Items": [
    { "DiscountID": "ACME-CONTRACT-2023", "BuyerID": "acme-corp" },
    { "DiscountID": "LEGACY-VOLUME-2022", "BuyerGroupID": "enterprise-customers" }
  ]
}
```

Each item identifies one assignment to remove.

Behavior:

- If both the discount and party exist, but no assignment exists, this error is caught and the request is ignored.
- If either the discount or the party does not exist, this error is surfaced in the error payload.


## Authorization

| Role | Access |
| ---------- | ---------- |
| `PriceScheduleAdmin` | Full access to bulk upsert and delete operations for price schedules, discounts, and discount assignments. |
| `PriceScheduleReader` | Read-only access to bulk jobs and bulk job errors. |

## Related reading

- [Discounts](https://doc.sitecore.com/ordercloud/en/developers/platform/discounts.html)
- [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)

