Skip to content

Public APIs for Volume Pricing (VP)

Programmatically create, update, retrieve, and delete Volume Pricing (Quantity Break) rules via REST API.

  1. Get your API Key. If you don’t have one yet, please follow this document to generate it.
  2. This page mixes v1 and v2 endpoints: the header name is different for each:
Version Header Also accepted
v2 (GET /api/v2/vp/rules) x-api-key accessKey in body
v1 (everything below ## v1: Write Operations) x-bss-b2b-api-key accessKey in body

Version Scope Status
v2 (recommended) List and read rules with pagination and filtering ✅ Current
v1 write endpoints Create, update, delete rules ✅ Active
v1 read endpoints (get-by-domain, get-by-id) Retrieve rules ⚠️ Deprecated: use v2 instead

Why migrate read operations to v2?
v2 uses cursor-based pagination and field selection, returning only the data you need: significantly improving performance compared to v1.


GET /api/v2/vp/rules

Base URL: https://b2b-solution-public-api.bsscommerce.com


Method Where Value
Header x-api-key Your Access Key

domain is required: pass it as a query parameter (?domain=) or in the request body.

Example:

Terminal window
curl "https://b2b-solution-public-api.bsscommerce.com/api/v2/vp/rules?domain=your-shop.myshopify.com&limit=10" \
-H "x-api-key: YOUR_ACCESS_KEY"

Parameter Type Default Description
domain string - Your shop domain (required)
limit integer 30 Rules per page. Min: 1, max: 250
sort enum id Sort field: id | priority | createdAt
order enum see note Sort direction: asc | desc. Default: asc for priority, desc for id and createdAt
status enum - Filter by status: enable | disable
name string - Filter by rule name (case-insensitive substring match)
ruleSetting enum - Filter by setting type: quantity | value
ruleType enum - Filter by scope: product | order | variant
ids string - Comma-separated rule IDs (max 100). When present, all pagination params are ignored.
fields string - Comma-separated field names to return. id is always included.
nextCursor string - Cursor for the next page (from previous response)
previousCursor string - Cursor for the previous page (from previous response)

Top-level rule fields:

id, name, priority, status, rule_type, rule_setting, qb_table_type,
apply_to, customer_ids, customer_tags,
exclude_from, exc_customers, exc_customer_tags,
product_condition_type, product_ids, product_collections, product_tags, product_variants,
exc_product_type, exc_specific_products, exc_product_collections, exc_product_tags, exc_product_variants,
market_condition_type, market_ids,
published_at, unpublished_at, start_date, end_date, start_time, end_time, enable_end_date,
createdAt, updatedAt

Nested fields (trigger a JOIN: omit if only rule metadata is needed):

qbRuleQtyTables - quantity break tiers (when rule_setting = 0)
abRuleQtyTables - amount break tiers (when rule_setting = 1)

Internal fields not exposed: shop_id, file_theme_index, deletedAt.


Success (HTTP 200):

{
"success": true,
"data": {
"rules": [
{
"id": 12345,
"name": "Wholesale tier",
"priority": 5,
"status": 1,
"rule_type": 0,
"rule_setting": 0,
"qbRuleQtyTables": [
{
"id": 9,
"rule_id": 12345,
"qty_from": 1,
"qty_to": 10,
"discount_type": 2,
"discount_value": "10.00"
}
]
}
],
"pageInfo": {
"limit": 50,
"count": 50,
"hasNext": true,
"hasPrevious": false,
"nextCursor": "eyJzIjoicHJpb3JpdHkiLCJvIjoiYXNjIiwidiI6WzUsMTIzNDVdfQ",
"previousCursor": null,
"sort": "priority",
"order": "asc"
}
},
"meta": {
"requestId": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": "2026-05-27T15:00:00.000Z",
"processingTimeMs": 42
}
}

Error (4xx / 5xx):

{
"success": false,
"error": {
"code": "ACCESS_KEY_INVALID",
"message": "accessKey does not match the shop"
},
"meta": {
"requestId": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": "2026-05-27T15:00:00.000Z"
}
}

pageInfo fields:

Field Type Description
limit integer The limit value used for this response
count integer Number of rules returned in this page
hasNext boolean true if a next page exists
hasPrevious boolean true if a previous page exists
nextCursor string | null Pass as nextCursor to get the next page. null when hasNext is false
previousCursor string | null Pass as previousCursor to get the previous page. null when hasPrevious is false
sort string Sort field used for this response
order string Sort direction used for this response

  1. First call: send no cursor. Read pageInfo.nextCursor from the response.
  2. Next page: pass ?nextCursor=<value> from the previous response.
  3. Previous page: pass ?previousCursor=<value> from the previous response.
  4. Never send both cursors in the same request: returns 400 INVALID_QUERY.
  5. Treat cursors as opaque strings: do not parse or modify them.
  6. Changing sort or order invalidates existing cursors: restart from the first page.

List rules sorted by priority (first page):

Terminal window
curl "https://b2b-solution-public-api.bsscommerce.com/api/v2/vp/rules?domain=your-shop.myshopify.com&limit=50&sort=priority&status=enable" \
-H "x-api-key: $ACCESS_KEY"

Next page:

Terminal window
curl "https://b2b-solution-public-api.bsscommerce.com/api/v2/vp/rules?domain=your-shop.myshopify.com&limit=50&sort=priority&status=enable&nextCursor=eyJzIjoicHJpb3JpdHkiLCJvIjoiYXNjIiwidiI6WzUsMTIzNDVdfQ" \
-H "x-api-key: $ACCESS_KEY"

Fetch specific rules by ID:

Terminal window
curl "https://b2b-solution-public-api.bsscommerce.com/api/v2/vp/rules?domain=your-shop.myshopify.com&ids=12345,67890,11111" \
-H "x-api-key: $ACCESS_KEY"

Reduce payload with field selection:

Terminal window
curl "https://b2b-solution-public-api.bsscommerce.com/api/v2/vp/rules?domain=your-shop.myshopify.com&fields=id,name,priority,status" \
-H "x-api-key: $ACCESS_KEY"

Incremental sync: most recently created first:

Terminal window
curl "https://b2b-solution-public-api.bsscommerce.com/api/v2/vp/rules?domain=your-shop.myshopify.com&sort=createdAt&order=desc&limit=100" \
-H "x-api-key: $ACCESS_KEY"

Default: 60 requests per minute per shop (fixed window).

Every v2 response includes these headers:

Header Description
X-RateLimit-Limit Total requests allowed per window
X-RateLimit-Remaining Requests remaining in the current window
X-RateLimit-Reset Unix timestamp (seconds) when the window resets
Retry-After (429 only) Seconds to wait before retrying

Monitor X-RateLimit-Remaining on every response. When it reaches 0, pause until X-RateLimit-Reset before sending the next request.


Code HTTP Cause Fix
ACCESS_KEY_MISSING 401 No Access Key provided Add x-api-key header
ACCESS_KEY_INVALID 401 Access Key does not match shop Verify key in app dashboard under Public API
DOMAIN_MISSING 400 No domain provided Add ?domain=your-shop.myshopify.com
SHOP_NOT_FOUND 404 Domain not found Check domain is correct and app is installed
PUBLIC_API_DISABLED 403 Public API not enabled Enable it in app dashboard under Public API
RATE_LIMIT_EXCEEDED 429 Rate limit exceeded Wait Retry-After seconds, then retry
INVALID_QUERY 400 Invalid query parameter Check error.message for which field failed
UPSTREAM_UNAVAILABLE 502 Internal service unreachable Retry after a short delay

v1: Write Operations (Create / Update / Delete)

Section titled “v1: Write Operations (Create / Update / Delete)”

⚠️ get-by-domain and get-by-id are deprecated. Use GET /api/v2/vp/rules instead: it is faster and supports pagination and filtering.

The endpoints below (save, bulk-save, delete, mass-delete, applied rules, price list) remain active and are still required for writing rules.

Base URL: https://b2b-solution-public-api.bsscommerce.com

All v1 requests use POST with Content-Type: application/json. Include domain and accessKey in every request body.


Understand these fields before calling the endpoints.

priority: Determines which rule takes precedence over others.

status

0: inactive
1: active

apply_to

0: All customers
1: Logged-in customers
2: Not-logged-in customers
3: Specific customers
4: Customer tags

exclude_customer

0: None
1: Customer tags
2: Specific customers

product_condition_type

0: All products
1: Specific products
2: Product collections
3: Product tags
4: Specific variants

exc_product_type

0: None
1: Specific products
2: Product collections
3: Product tags

rule_setting

0: Quantity break
1: Amount break

qb_table_type

0: Full range & Discounts
1: Full range & Discounted prices
2: From & Discounts
3: From & Discounted prices 1
4: From & Discounted prices 2
5: From & Discounts with Discounted prices 1
6: From & Discounts with Discounted prices 2
7: Legacy 1
8: Legacy 2

Note

  • When you get rules by domain or ID, rule_setting appears in the response to indicate whether a rule is Quantity-based or Volume-based.
  • When you create/update a rule, rule_setting will be Quantity-based by default (create/update Volume-based is not supported as of now).

rule_type

0: Minimum Product Qty
1: Minimum Order Qty
2: Minimum Variant Qty

Behavior explanations:

  • Minimum Product Qty
    If one order contains the selected products and the number of each product meets the quantity break ranges, the price of the product will be discounted accordingly.
    Example:
    Products A (variants A1, A2) and B (variants B1, B2) are selected.
    Ranges: 0–5 (−10%), 6–10 (−15%).
    If a customer buys 3×A1, 6×A2, 4×B1 → Total A qty = 9 → A gets −15%, B gets −10%.
    If qty is not within ranges, original prices apply.
  • Minimum Order Qty
    If one order contains the selected products and the total number of those products meets the ranges, they will be discounted accordingly.
    Example:
    Products A, B; A has A1, A2, B has B1, B2.
    Ranges: 0–5 (−10%), 6–10 (−15%), 11–20 (−20%).
    If customer buys 3×A1, 6×A2, 4×B1 → Total A+B qty = 13 → A & B get −20%.
    If qty not within ranges, original prices apply.
  • Minimum Variant Qty
    If one order contains the selected products and the number of variants meets the ranges, those variants are discounted accordingly.
    Example:
    Products A, B; variants A1, A2, B1, B2.
    Ranges: 0–5 (−10%), 6–10 (−15%), 11–20 (−20%).
    If customer buys 3×A1, 6×A2, 4×B1 → A1 & B1 get −10%, A2 gets −15%.
    If qty not within ranges, original prices apply.

Range fields:

  • qty_from: lower bound of a range
  • qty_to: upper bound of a range

discount_type

0: Apply a price to selected products
1: Decrease a fixed amount of the original prices of selected products
2: Decrease the original prices of selected products by a percentage (%)
2: Decrease the original price by a percentage (%)
Get rules by domain (Deprecated) - Expand to see detail

⚠️ Deprecated due to performance issues: use GET /api/v2/vp/rules instead.

POST /api/v1/qb/get-by-domain

Example

Terminal window
curl -X POST "https://b2b-solution-public-api.bsscommerce.com/api/v1/qb/get-by-domain" \
-H "Content-Type: application/json" \
-d '{"domain": "your-shop.myshopify.com", "accessKey": "YOUR_ACCESS_KEY"}'

Headers

Content-Type: application/json

Body

{
"domain": "your-shop.myshopify.com",
"accessKey": "YOUR_ACCESS_KEY"
}

Response 200

{
"success": true,
"rules": [
{
"id": 9792,
"shop_id": 9204,
"name": "sencond1",
"priority": 0,
"rule_type": 2,
"rule_setting": 0,
"status": 1,
"apply_to": 0,
"customer_ids": [],
"customer_tags": [],
"product_condition_type": 0,
"product_ids": [],
"product_collections": [],
"product_tags": [],
"exc_product_type": 1,
"exc_specific_products": [6669588136111, 6669469352111],
"exc_product_collections": [],
"exc_product_tags": [],
"published_at": null,
"unpublished_at": null,
"exc_customer_tags": "",
"exclude_from": 0,
"exc_customers": "",
"qb_table_type": 3,
"createdAt": "2023-03-09T02:27:26.000Z",
"updatedAt": "2023-05-30T04:29:18.000Z",
"qbRuleQtyTables": [
{
"id": 33022,
"rule_id": 9792,
"qty_from": 1,
"qty_to": 4,
"discount_type": 0,
"discount_value": 10
},
{
"id": 33023,
"rule_id": 9792,
"qty_from": 6,
"qty_to": 8,
"discount_type": 1,
"discount_value": 10
}
],
"abRuleQtyTables": []
}
]
}

Get rule by ID (Deprecated) - Expand to see detail

⚠️ Deprecated due to performance issues: use GET /api/v2/vp/rules?ids=<id> instead.

POST /api/v1/qb/get-by-id

Example

Terminal window
curl -X POST "https://b2b-solution-public-api.bsscommerce.com/api/v1/qb/get-by-id" \
-H "Content-Type: application/json" \
-d '{"domain": "your-shop.myshopify.com", "accessKey": "YOUR_ACCESS_KEY", "id": 46}'

Headers

Content-Type: application/json

Body

{
"domain": "your-shop.myshopify.com",
"accessKey": "YOUR_ACCESS_KEY",
"id": 46
}

Response 200

{
"success": true,
"rule": {
"id": 46,
"name": "sencond1",
"priority": 0,
"status": 1,
"apply_to": 0,
"customer_ids": [],
"customer_tags": [],
"exclude_from": 0,
"exc_customers": [],
"exc_customer_tags": [],
"product_condition_type": 0,
"product_ids": [],
"product_collections": [],
"product_tags": [],
"variant_ids": [],
"exc_product_type": 3,
"exc_specific_products": [],
"exc_product_tags": ["babytshirt", "gaminggear"],
"exc_product_collections": [],
"rule_setting": 0,
"rule_type": 2,
"qty_table": [
{
"id": 33022,
"rule_id": 9792,
"qty_from": 1,
"qty_to": 4,
"discount_type": 0,
"discount_value": 10
},
{
"id": 33023,
"rule_id": 9792,
"qty_from": 6,
"qty_to": 8,
"discount_type": 1,
"discount_value": 10
}
],
"amount_table": [],
"qb_table_type": 3,
"createdAt": "2023-03-09T02:27:26.000Z",
"updatedAt": "2023-05-30T04:29:18.000Z"
}
}
POST /api/v1/qb/save

Rules:

  • If there is no id, a new rule is created.
  • If id is present, that rule is updated.
  • If product_condition_type = 4 (specific variants), rule_type must not be 0 (minimum product qty).

Request body:

{
"domain": "your-shop.myshopify.com",
"accessKey": "YOUR_ACCESS_KEY",
"rule": {
"id": 11,
"name": "Wholesale tier",
"priority": 0,
"status": 1,
"apply_to": 0,
"customer_ids": [],
"customer_tags": [],
"exclude_from": 0,
"exc_customers": [],
"exc_customer_tags": [],
"product_condition_type": 0,
"product_ids": [],
"product_collections": [],
"product_tags": [],
"variant_ids": [],
"exc_product_type": 1,
"exc_specific_products": [4766764787771, 4766764787752],
"exc_product_tags": [],
"exc_product_collections": [],
"rule_setting": 0,
"rule_type": 2,
"qty_table": [
{ "qty_from": 1, "qty_to": 4, "discount_type": 0, "discount_value": 10 },
{ "qty_from": 6, "qty_to": 8, "discount_type": 1, "discount_value": 10 }
],
"amount_table": [],
"qb_table_type": 1
}
}

Response 200:

{
"success": true,
"ruleId": 11,
"message": "Updated the rule successfully"
}

POST /api/v1/qb/bulk-save

Rules:

  • Rules without an id are created.
  • Rules with an id are updated.
  • If an id is not found, those rules are skipped.

Request body:

{
"domain": "your-shop.myshopify.com",
"accessKey": "YOUR_ACCESS_KEY",
"rules": [
{
"name": "Tier A",
"priority": 0,
"status": 1,
"apply_to": 0,
"product_condition_type": 0,
"customer_ids": [],
"customer_tags": [],
"product_ids": [],
"product_collections": [],
"product_tags": [],
"variant_ids": [],
"exc_customer_tags": "",
"exclude_from": 0,
"exc_customers": "",
"exc_product_type": 0,
"exc_specific_products": [],
"exc_product_tags": [],
"exc_product_collections": [],
"rule_type": 2,
"rule_setting": 0,
"qty_table": [
{ "qty_from": 1, "qty_to": 4, "discount_type": 0, "discount_value": 10 },
{ "qty_from": 6, "qty_to": 8, "discount_type": 1, "discount_value": 10 }
],
"amount_table": [],
"qb_table_type": 4
}
]
}

Response 200:

{
"success": true,
"message": [
"Rule Tier A has been created successfully"
]
}

POST /api/v1/qb/delete

Request body:

{
"domain": "your-shop.myshopify.com",
"accessKey": "YOUR_ACCESS_KEY",
"id": 79
}

Response 200:

{
"success": true,
"message": "Deleted rule successfully"
}

POST /api/v1/qb/mass-delete

Request body:

{
"domain": "your-shop.myshopify.com",
"accessKey": "YOUR_ACCESS_KEY",
"ids": [79, 80, 81]
}

Response 200:

{
"success": true,
"message": "Deleted multiple qb rule successfully"
}

Returns which rules and discount tiers apply to specific products for a given customer.

POST /api/v1/qb/get-products-applied-rules

If customer_id is null, the system checks rules that apply to All customers or Not-logged-in customers.

Request body:

{
"domain": "your-shop.myshopify.com",
"accessKey": "YOUR_ACCESS_KEY",
"product_ids": [6103930831040, 6103930929344],
"customer_id": 5110452355264
}

Response 200:

{
"success": true,
"productsAppliedRule": [
{
"id": "6103930831040",
"rule_name": "Wholesale tier",
"rule_id": 51,
"qty_table": [
{ "id": 47, "qty_from": 1, "qty_to": 3, "discount_type": 0, "discount_value": 10 },
{ "id": 48, "qty_from": 6, "qty_to": 7, "discount_type": 1, "discount_value": 10 }
]
}
]
}

Get Price List of Variants Based on Applied Rules

Section titled “Get Price List of Variants Based on Applied Rules”

Returns calculated variant prices after all applicable Volume Pricing rules are applied for a customer.

POST /api/v1/qb/get-variants-price-list

If customer_id is null, the system checks rules that apply to All or Not-logged-in customers.

Request body:

{
"domain": "your-shop.myshopify.com",
"accessKey": "YOUR_ACCESS_KEY",
"product_ids": [6103930831040, 6103930929344],
"customer_id": 5110452355264
}

Response 200:

{
"success": true,
"priceList": [
{
"id": "6103930831040",
"rule_name": "Wholesale tier",
"rule_id": 52,
"variants": [
{
"id": "37682508955840",
"price": "36.00",
"compareAtPrice": null,
"appliedRulePrice": [
{ "qty_from": 6, "qty_to": 7, "discount_type": 1, "discount_value": 10, "modifiedPrice": 90 },
{ "qty_from": 1, "qty_to": 3, "discount_type": 0, "discount_value": 10, "modifiedPrice": 10 }
]
}
]
}
]
}

POST /api/v1/product/search

afterIndex is a cursor: pass null to start from the beginning.

Request body:

{
"domain": "your-shop.myshopify.com",
"accessKey": "YOUR_ACCESS_KEY",
"afterIndex": null,
"first": 20,
"searchQuery": "ocean"
}
POST /api/v1/product/get-tags
{ "domain": "your-shop.myshopify.com", "accessKey": "YOUR_ACCESS_KEY" }
POST /api/v1/product/get-by-tags
{
"domain": "your-shop.myshopify.com",
"accessKey": "YOUR_ACCESS_KEY",
"afterIndex": null,
"first": 20,
"tags": ["tag1", "tag2"],
"operation": "AND"
}
  • "AND": returned products contain all listed tags
  • "OR": returned products contain any of the listed tags
POST /api/v1/product/get-by-ids
{
"domain": "your-shop.myshopify.com",
"accessKey": "YOUR_ACCESS_KEY",
"ids": [6586590625965]
}

POST /api/v1/customer/search
{
"domain": "your-shop.myshopify.com",
"accessKey": "YOUR_ACCESS_KEY",
"afterIndex": null,
"first": 20,
"searchQuery": "john"
}
POST /api/v1/customer/get-tags
{ "domain": "your-shop.myshopify.com", "accessKey": "YOUR_ACCESS_KEY" }
POST /api/v1/customer/get-by-ids
{
"domain": "your-shop.myshopify.com",
"accessKey": "YOUR_ACCESS_KEY",
"ids": [5127974846637]
}
POST /api/v1/customer/get-by-tags
{
"domain": "your-shop.myshopify.com",
"accessKey": "YOUR_ACCESS_KEY",
"afterIndex": null,
"first": 20,
"tags": ["wholesale", "vip"],
"operation": "OR"
}
  • "AND": returned customers must have all listed tags
  • "OR": returned customers need any one of the listed tags

  • v1 processes one request at a time. After a request completes, the next one is processed.
  • In case of a server error, recovery may take up to 3 minutes before a new request can be processed.