Group B2B customers and apply automatic percentage discounts
/admin/api/discount-groups
List every discount group ordered by name, each annotated with its member and rule counts. Rules and members themselves are not expanded here — fetch a single group for those.
/admin/api/discount-groups/{id}
Fetch a single discount group with its per-category rules (joined to category name and slug) and its customer members fully expanded.
/admin/api/discount-groups
Create a discount group. The optional default_percent_off_bp applies storewide unless overridden by a per-category rule. Returns only the new id; fetch the group to read it back.
/admin/api/discount-groups/{id}
Partial update of a discount group. Omitted fields are left unchanged. For the nullable fields description and default_percent_off_bp, sending an explicit null clears the stored value, while omitting the key leaves it as-is. Returns the full updated group detail.
/admin/api/discount-groups/{id}
Permanently delete a discount group.
/admin/api/discount-groups/{id}/rules
Full-replace the per-category discount rules for a group. The supplied list becomes the complete rule set — existing rules not present are removed, and an empty list clears all rules. Each rule overrides the group's default for products in that category. Read the rules back via GET /admin/api/discount-groups/{id}.
/admin/api/discount-groups/{id}/members
Add a customer to a discount group. This also flips the customer's is_b2b flag to true. List a group's current members via GET /admin/api/discount-groups/{id}.
/admin/api/discount-groups/{id}/members/{customer_id}
Remove a customer from a discount group. The customer's is_b2b flag stays true so they can be re-assigned to another group without losing B2B status — only the group assignment is cleared.
/admin/api/discount-groups
List every discount group ordered by name, each annotated with its member and rule counts. Rules and members themselves are not expanded here — fetch a single group for those.
{
"groups": [
{
"id": 1,
"name": "Wholesale",
"slug": "wholesale",
"description": "Standard B2B reseller pricing.",
"default_percent_off_bp": 1500,
"member_count": 12,
"rule_count": 3,
"created_at": "2026-04-15T09:00:00Z",
"updated_at": "2026-04-15T09:00:00Z"
},
{
"id": 2,
"name": "VIP Partners",
"slug": "vip-partners",
"description": null,
"default_percent_off_bp": null,
"member_count": 4,
"rule_count": 0,
"created_at": "2026-04-20T11:15:00Z",
"updated_at": "2026-04-20T11:15:00Z"
}
]
}
curl "https://yourshop.zeroshop.io/admin/api/discount-groups" \
-H "Authorization: Bearer zspat_..."
import requests
resp = requests.get(
"https://yourshop.zeroshop.io/admin/api/discount-groups",
headers={"Authorization": "Bearer zspat_..."}
)
groups = resp.json()["groups"]
const resp = await fetch("https://yourshop.zeroshop.io/admin/api/discount-groups", {
headers: { "Authorization": "Bearer zspat_..." }
});
const { groups } = await resp.json();
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/discount-groups")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer zspat_..."
resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
groups = JSON.parse(resp.body)["groups"]
/admin/api/discount-groups/{id}
Fetch a single discount group with its per-category rules (joined to category name and slug) and its customer members fully expanded.
{
"id": 1,
"name": "Wholesale",
"slug": "wholesale",
"description": "Standard B2B reseller pricing.",
"default_percent_off_bp": 1500,
"created_at": "2026-04-15T09:00:00Z",
"updated_at": "2026-04-15T09:00:00Z",
"rules": [
{
"category_id": 10,
"category_name": "Footwear",
"category_slug": "footwear",
"percent_off_bp": 2000
},
{
"category_id": 11,
"category_name": "Accessories",
"category_slug": "accessories",
"percent_off_bp": 1000
}
],
"members": [
{
"id": 501,
"name": "Acme Retail Ltd",
"email": "orders@acme-retail.example"
},
{
"id": 502,
"name": "Globex Trading",
"email": "buyer@globex.example"
}
]
}
curl "https://yourshop.zeroshop.io/admin/api/discount-groups/1" \
-H "Authorization: Bearer zspat_..."
import requests
resp = requests.get(
"https://yourshop.zeroshop.io/admin/api/discount-groups/1",
headers={"Authorization": "Bearer zspat_..."}
)
group = resp.json()
const resp = await fetch("https://yourshop.zeroshop.io/admin/api/discount-groups/1", {
headers: { "Authorization": "Bearer zspat_..." }
});
const group = await resp.json();
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/discount-groups/1")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer zspat_..."
resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
group = JSON.parse(resp.body)
/admin/api/discount-groups
Create a discount group. The optional default_percent_off_bp applies storewide unless overridden by a per-category rule. Returns only the new id; fetch the group to read it back.
{
"name": "Wholesale",
"slug": "wholesale",
"description": "Standard B2B reseller pricing.",
"default_percent_off_bp": 1500
}
{
"id": 7
}
curl -X POST "https://yourshop.zeroshop.io/admin/api/discount-groups" \
-H "Authorization: Bearer zspat_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Wholesale",
"slug": "wholesale",
"description": "Standard B2B reseller pricing.",
"default_percent_off_bp": 1500
}'
import requests
resp = requests.post(
"https://yourshop.zeroshop.io/admin/api/discount-groups",
headers={"Authorization": "Bearer zspat_..."},
json={
"name": "Wholesale",
"slug": "wholesale",
"description": "Standard B2B reseller pricing.",
"default_percent_off_bp": 1500
}
)
group_id = resp.json()["id"]
const resp = await fetch("https://yourshop.zeroshop.io/admin/api/discount-groups", {
method: "POST",
headers: {
"Authorization": "Bearer zspat_...",
"Content-Type": "application/json"
},
body: JSON.stringify({
name: "Wholesale",
slug: "wholesale",
description: "Standard B2B reseller pricing.",
default_percent_off_bp: 1500
})
});
const { id } = await resp.json();
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/discount-groups")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer zspat_..."
req["Content-Type"] = "application/json"
req.body = {
name: "Wholesale",
slug: "wholesale",
description: "Standard B2B reseller pricing.",
default_percent_off_bp: 1500
}.to_json
resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
group_id = JSON.parse(resp.body)["id"]
/admin/api/discount-groups/{id}
Partial update of a discount group. Omitted fields are left unchanged. For the nullable fields description and default_percent_off_bp, sending an explicit null clears the stored value, while omitting the key leaves it as-is. Returns the full updated group detail.
{
"default_percent_off_bp": 2000
}
{
"id": 1,
"name": "Wholesale",
"slug": "wholesale",
"description": "Standard B2B reseller pricing.",
"default_percent_off_bp": 2000,
"created_at": "2026-04-15T09:00:00Z",
"updated_at": "2026-05-03T10:05:00Z",
"rules": [
{
"category_id": 10,
"category_name": "Footwear",
"category_slug": "footwear",
"percent_off_bp": 2000
}
],
"members": [
{
"id": 501,
"name": "Acme Retail Ltd",
"email": "orders@acme-retail.example"
}
]
}
curl -X PUT "https://yourshop.zeroshop.io/admin/api/discount-groups/1" \
-H "Authorization: Bearer zspat_..." \
-H "Content-Type: application/json" \
-d '{"default_percent_off_bp": 2000}'
import requests
resp = requests.put(
"https://yourshop.zeroshop.io/admin/api/discount-groups/1",
headers={"Authorization": "Bearer zspat_..."},
json={"default_percent_off_bp": 2000}
)
group = resp.json()
const resp = await fetch("https://yourshop.zeroshop.io/admin/api/discount-groups/1", {
method: "PUT",
headers: {
"Authorization": "Bearer zspat_...",
"Content-Type": "application/json"
},
body: JSON.stringify({ default_percent_off_bp: 2000 })
});
const group = await resp.json();
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/discount-groups/1")
req = Net::HTTP::Put.new(uri)
req["Authorization"] = "Bearer zspat_..."
req["Content-Type"] = "application/json"
req.body = { default_percent_off_bp: 2000 }.to_json
resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
group = JSON.parse(resp.body)
/admin/api/discount-groups/{id}
Permanently delete a discount group.
curl -X DELETE "https://yourshop.zeroshop.io/admin/api/discount-groups/1" \
-H "Authorization: Bearer zspat_..."
import requests
requests.delete(
"https://yourshop.zeroshop.io/admin/api/discount-groups/1",
headers={"Authorization": "Bearer zspat_..."}
)
await fetch("https://yourshop.zeroshop.io/admin/api/discount-groups/1", {
method: "DELETE",
headers: { "Authorization": "Bearer zspat_..." }
});
require "net/http"
uri = URI("https://yourshop.zeroshop.io/admin/api/discount-groups/1")
req = Net::HTTP::Delete.new(uri)
req["Authorization"] = "Bearer zspat_..."
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
/admin/api/discount-groups/{id}/rules
Full-replace the per-category discount rules for a group. The supplied list becomes the complete rule set — existing rules not present are removed, and an empty list clears all rules. Each rule overrides the group's default for products in that category. Read the rules back via GET /admin/api/discount-groups/{id}.
{
"rules": [
{
"category_id": 10,
"percent_off_bp": 2000
},
{
"category_id": 11,
"percent_off_bp": 1000
}
]
}
curl -X PUT "https://yourshop.zeroshop.io/admin/api/discount-groups/1/rules" \
-H "Authorization: Bearer zspat_..." \
-H "Content-Type: application/json" \
-d '{
"rules": [
{"category_id": 10, "percent_off_bp": 2000},
{"category_id": 11, "percent_off_bp": 1000}
]
}'
import requests
resp = requests.put(
"https://yourshop.zeroshop.io/admin/api/discount-groups/1/rules",
headers={"Authorization": "Bearer zspat_..."},
json={
"rules": [
{"category_id": 10, "percent_off_bp": 2000},
{"category_id": 11, "percent_off_bp": 1000}
]
}
)
await fetch("https://yourshop.zeroshop.io/admin/api/discount-groups/1/rules", {
method: "PUT",
headers: {
"Authorization": "Bearer zspat_...",
"Content-Type": "application/json"
},
body: JSON.stringify({
rules: [
{ category_id: 10, percent_off_bp: 2000 },
{ category_id: 11, percent_off_bp: 1000 }
]
})
});
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/discount-groups/1/rules")
req = Net::HTTP::Put.new(uri)
req["Authorization"] = "Bearer zspat_..."
req["Content-Type"] = "application/json"
req.body = {
rules: [
{ category_id: 10, percent_off_bp: 2000 },
{ category_id: 11, percent_off_bp: 1000 }
]
}.to_json
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
/admin/api/discount-groups/{id}/members
Add a customer to a discount group. This also flips the customer's is_b2b flag to true. List a group's current members via GET /admin/api/discount-groups/{id}.
{
"customer_id": 501
}
curl -X POST "https://yourshop.zeroshop.io/admin/api/discount-groups/1/members" \
-H "Authorization: Bearer zspat_..." \
-H "Content-Type: application/json" \
-d '{"customer_id": 501}'
import requests
resp = requests.post(
"https://yourshop.zeroshop.io/admin/api/discount-groups/1/members",
headers={"Authorization": "Bearer zspat_..."},
json={"customer_id": 501}
)
await fetch("https://yourshop.zeroshop.io/admin/api/discount-groups/1/members", {
method: "POST",
headers: {
"Authorization": "Bearer zspat_...",
"Content-Type": "application/json"
},
body: JSON.stringify({ customer_id: 501 })
});
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/discount-groups/1/members")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer zspat_..."
req["Content-Type"] = "application/json"
req.body = { customer_id: 501 }.to_json
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
/admin/api/discount-groups/{id}/members/{customer_id}
Remove a customer from a discount group. The customer's is_b2b flag stays true so they can be re-assigned to another group without losing B2B status — only the group assignment is cleared.
curl -X DELETE "https://yourshop.zeroshop.io/admin/api/discount-groups/1/members/501" \
-H "Authorization: Bearer zspat_..."
import requests
requests.delete(
"https://yourshop.zeroshop.io/admin/api/discount-groups/1/members/501",
headers={"Authorization": "Bearer zspat_..."}
)
await fetch("https://yourshop.zeroshop.io/admin/api/discount-groups/1/members/501", {
method: "DELETE",
headers: { "Authorization": "Bearer zspat_..." }
});
require "net/http"
uri = URI("https://yourshop.zeroshop.io/admin/api/discount-groups/1/members/501")
req = Net::HTTP::Delete.new(uri)
req["Authorization"] = "Bearer zspat_..."
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
We value your privacy
We use cookies for essential site functionality and, with your consent, analytics to understand how our platform is used. No personal data is shared with third parties. See our Privacy Policy for details.