Public APIs for Volume Pricing (VP)
Programmatically create, update, retrieve, and delete Volume Pricing (Quantity Break) rules via REST API.
Get the Access Key
Section titled “Get the Access Key”- Get your API Key. If you don’t have one yet, please follow this document to generate it.
- 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 |
API Versions
Section titled “API Versions”| 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.
v2: List Volume Pricing Rules
Section titled “v2: List Volume Pricing Rules”v2 - Get rules by domain or ID
Section titled “v2 - Get rules by domain or ID”GET /api/v2/vp/rules
Base URL: https://b2b-solution-public-api.bsscommerce.com
Authentication
Section titled “Authentication”| 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:
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"Query Parameters
Section titled “Query Parameters”| 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) |
Available Fields (?fields=)
Section titled “Available Fields (?fields=)”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, updatedAtNested 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.
Response Envelope
Section titled “Response Envelope”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 |
Cursor Pagination
Section titled “Cursor Pagination”- First call: send no cursor. Read
pageInfo.nextCursorfrom the response. - Next page: pass
?nextCursor=<value>from the previous response. - Previous page: pass
?previousCursor=<value>from the previous response. - Never send both cursors in the same request: returns
400 INVALID_QUERY. - Treat cursors as opaque strings: do not parse or modify them.
- Changing
sortororderinvalidates existing cursors: restart from the first page.
Examples
Section titled “Examples”List rules sorted by priority (first page):
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:
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:
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:
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:
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"Rate Limits
Section titled “Rate Limits”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.
Error Codes
Section titled “Error Codes”| 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-domainandget-by-idare deprecated. UseGET /api/v2/vp/rulesinstead: 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.
Rule Model: Fields & Enums
Section titled “Rule Model: Fields & Enums”Understand these fields before calling the endpoints.
priority: Determines which rule takes precedence over others.
status
0: inactive1: activeapply_to
0: All customers1: Logged-in customers2: Not-logged-in customers3: Specific customers4: Customer tagsexclude_customer
0: None1: Customer tags2: Specific customersproduct_condition_type
0: All products1: Specific products2: Product collections3: Product tags4: Specific variantsexc_product_type
0: None1: Specific products2: Product collections3: Product tagsrule_setting
0: Quantity break1: Amount breakqb_table_type
0: Full range & Discounts1: Full range & Discounted prices2: From & Discounts3: From & Discounted prices 14: From & Discounted prices 25: From & Discounts with Discounted prices 16: From & Discounts with Discounted prices 27: Legacy 18: Legacy 2Note
- When you get rules by domain or ID,
rule_settingappears in the response to indicate whether a rule is Quantity-based or Volume-based.- When you create/update a rule,
rule_settingwill be Quantity-based by default (create/update Volume-based is not supported as of now).
qty_table: Quantity Settings
Section titled “qty_table: Quantity Settings”rule_type
0: Minimum Product Qty1: Minimum Order Qty2: Minimum Variant QtyBehavior 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 rangeqty_to: upper bound of a range
discount_type
0: Apply a price to selected products1: Decrease a fixed amount of the original prices of selected products2: Decrease the original prices of selected products by a percentage (%)2: Decrease the original price by a percentage (%)Get rule by domain (Deprecated)
Section titled “Get rule by domain (Deprecated)”Get rules by domain (Deprecated) - Expand to see detail
⚠️ Deprecated due to performance issues: use
GET /api/v2/vp/rulesinstead.
POST /api/v1/qb/get-by-domainExample
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/jsonBody
{ "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 rules by (Deprecated)
Section titled “Get rules by (Deprecated)”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-idExample
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/jsonBody
{ "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" }}Create or Update a Single Rule
Section titled “Create or Update a Single Rule”POST /api/v1/qb/saveRules:
- If there is no
id, a new rule is created. - If
idis present, that rule is updated. - If
product_condition_type = 4(specific variants),rule_typemust not be0(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"}Create or Update Multiple Rules
Section titled “Create or Update Multiple Rules”POST /api/v1/qb/bulk-saveRules:
- Rules without an
idare created. - Rules with an
idare updated. - If an
idis 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" ]}Delete a Rule
Section titled “Delete a Rule”POST /api/v1/qb/deleteRequest body:
{ "domain": "your-shop.myshopify.com", "accessKey": "YOUR_ACCESS_KEY", "id": 79}Response 200:
{ "success": true, "message": "Deleted rule successfully"}Delete Multiple Rules
Section titled “Delete Multiple Rules”POST /api/v1/qb/mass-deleteRequest body:
{ "domain": "your-shop.myshopify.com", "accessKey": "YOUR_ACCESS_KEY", "ids": [79, 80, 81]}Response 200:
{ "success": true, "message": "Deleted multiple qb rule successfully"}Get Applied Rules for Products
Section titled “Get Applied Rules for Products”Returns which rules and discount tiers apply to specific products for a given customer.
POST /api/v1/qb/get-products-applied-rulesIf
customer_idisnull, 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-listIf
customer_idisnull, 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 } ] } ] } ]}Product Endpoints
Section titled “Product Endpoints”Search Products
Section titled “Search Products”POST /api/v1/product/searchafterIndex 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"}Get Product Tags
Section titled “Get Product Tags”POST /api/v1/product/get-tags{ "domain": "your-shop.myshopify.com", "accessKey": "YOUR_ACCESS_KEY" }Get Products by Tags
Section titled “Get Products by Tags”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
Get Products by IDs
Section titled “Get Products by IDs”POST /api/v1/product/get-by-ids{ "domain": "your-shop.myshopify.com", "accessKey": "YOUR_ACCESS_KEY", "ids": [6586590625965]}Customer Endpoints
Section titled “Customer Endpoints”Search Customers
Section titled “Search Customers”POST /api/v1/customer/search{ "domain": "your-shop.myshopify.com", "accessKey": "YOUR_ACCESS_KEY", "afterIndex": null, "first": 20, "searchQuery": "john"}Get Customer Tags
Section titled “Get Customer Tags”POST /api/v1/customer/get-tags{ "domain": "your-shop.myshopify.com", "accessKey": "YOUR_ACCESS_KEY" }Get Customers by IDs
Section titled “Get Customers by IDs”POST /api/v1/customer/get-by-ids{ "domain": "your-shop.myshopify.com", "accessKey": "YOUR_ACCESS_KEY", "ids": [5127974846637]}Get Customers by Tags
Section titled “Get Customers by Tags”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
Request Handling Policy (v1)
Section titled “Request Handling Policy (v1)”- 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.