Products & Plans
The product catalog defines what you sell. It has three layers:
Product → Plan → SKU (price)
Products
A Product is a named offering (e.g., “API Platform”, “Storage”, “Support Tier”).
# [curl]
# List products
curl https://api.bill.sh/admin/v1/catalog/products \
-H "Authorization: Bearer $ADMIN_TOKEN"
# Create a product
curl -X POST https://api.bill.sh/admin/v1/catalog/products \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "API Platform",
"description": "Usage-based API access",
"tags": ["api", "core"]
}'
# [Python]
import requests
# List products
products = requests.get(
"https://api.bill.sh/admin/v1/catalog/products",
headers={"Authorization": f"Bearer {ADMIN_TOKEN}"},
).json()
# Create a product
resp = requests.post(
"https://api.bill.sh/admin/v1/catalog/products",
headers={"Authorization": f"Bearer {ADMIN_TOKEN}"},
json={
"name": "API Platform",
"description": "Usage-based API access",
"tags": ["api", "core"],
},
)
product = resp.json()
product_id = product["id"]
print(f"Product: {product_id}")
// [Node.js]
// List products
const productsResp = await fetch("https://api.bill.sh/admin/v1/catalog/products", {
headers: { "Authorization": `Bearer ${ADMIN_TOKEN}` },
});
const products = await productsResp.json();
// Create a product
const resp = await fetch("https://api.bill.sh/admin/v1/catalog/products", {
method: "POST",
headers: {
"Authorization": `Bearer ${ADMIN_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "API Platform",
description: "Usage-based API access",
tags: ["api", "core"],
}),
});
const product = await resp.json();
console.log("Product:", product.id);
// [Go]
// List products
req, _ := http.NewRequest("GET", "https://api.bill.sh/admin/v1/catalog/products", nil)
req.Header.Set("Authorization", "Bearer "+adminToken)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var products []map[string]interface{}
json.NewDecoder(resp.Body).Decode(&products)
// Create a product
body, _ := json.Marshal(map[string]interface{}{
"name": "API Platform",
"description": "Usage-based API access",
"tags": []string{"api", "core"},
})
req2, _ := http.NewRequest("POST", "https://api.bill.sh/admin/v1/catalog/products",
bytes.NewReader(body))
req2.Header.Set("Authorization", "Bearer "+adminToken)
req2.Header.Set("Content-Type", "application/json")
resp2, _ := http.DefaultClient.Do(req2)
defer resp2.Body.Close()
var product map[string]interface{}
json.NewDecoder(resp2.Body).Decode(&product)
Plans
A Plan bundles one or more SKUs together into a subscribable offering.
# [curl]
curl -X POST https://api.bill.sh/admin/v1/catalog/plans \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Startup",
"description": "For teams up to 10 users",
"billing_cadence": "Monthly",
"trial_days": 14
}'
# [Python]
resp = requests.post(
"https://api.bill.sh/admin/v1/catalog/plans",
headers={"Authorization": f"Bearer {ADMIN_TOKEN}"},
json={
"name": "Startup",
"description": "For teams up to 10 users",
"billing_cadence": "Monthly",
"trial_days": 14,
},
)
plan = resp.json()
plan_id = plan["id"]
print(f"Plan ID: {plan_id}")
// [Node.js]
const resp = await fetch("https://api.bill.sh/admin/v1/catalog/plans", {
method: "POST",
headers: {
"Authorization": `Bearer ${ADMIN_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Startup",
description: "For teams up to 10 users",
billing_cadence: "Monthly",
trial_days: 14,
}),
});
const plan = await resp.json();
console.log("Plan ID:", plan.id);
// [Go]
body, _ := json.Marshal(map[string]interface{}{
"name": "Startup",
"description": "For teams up to 10 users",
"billing_cadence": "Monthly",
"trial_days": 14,
})
req, _ := http.NewRequest("POST", "https://api.bill.sh/admin/v1/catalog/plans",
bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+adminToken)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var plan map[string]interface{}
json.NewDecoder(resp.Body).Decode(&plan)
fmt.Println("Plan ID:", plan["id"])
SKUs (Price Points)
A SKU defines how a plan charges. Supported pricing models:
| Model | Description | Use case |
|---|---|---|
Flat | Fixed monthly fee | Seat license, base platform fee |
PerUnit | Per-unit price × quantity | Per API call, per GB |
Graduated | Tiered pricing, each tier applies to quantity in that tier | Usage-based |
Volume | All units at the tier price that the total quantity falls into | Volume discounts |
Package | Price per bundle of N units | Credits pack |
Committed | Minimum commit + overage rate | Enterprise committed use |
# [curl]
curl -X POST https://api.bill.sh/admin/v1/catalog/skus \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"plan_id": "01944b1f-0000-7000-8000-000000000002",
"name": "API Requests",
"pricing_model": "PerUnit",
"unit_amount_nanos": "1000000",
"meter_event_type": "api.request",
"currency": "USD"
}'
# [Python]
resp = requests.post(
"https://api.bill.sh/admin/v1/catalog/skus",
headers={"Authorization": f"Bearer {ADMIN_TOKEN}"},
json={
"plan_id": plan_id,
"name": "API Requests",
"pricing_model": "PerUnit",
"unit_amount_nanos": "1000000", # $0.000001 per request
"meter_event_type": "api.request",
"currency": "USD",
},
)
sku = resp.json()
print(f"SKU {sku['id']} — {sku['pricing_model']} at {sku['unit_amount_nanos']} nanos/unit")
// [Node.js]
const resp = await fetch("https://api.bill.sh/admin/v1/catalog/skus", {
method: "POST",
headers: {
"Authorization": `Bearer ${ADMIN_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
plan_id: planId,
name: "API Requests",
pricing_model: "PerUnit",
unit_amount_nanos: "1000000", // $0.000001 per request
meter_event_type: "api.request",
currency: "USD",
}),
});
const sku = await resp.json();
console.log(`SKU ${sku.id} — ${sku.pricing_model}`);
// [Go]
body, _ := json.Marshal(map[string]interface{}{
"plan_id": planID,
"name": "API Requests",
"pricing_model": "PerUnit",
"unit_amount_nanos": "1000000",
"meter_event_type": "api.request",
"currency": "USD",
})
req, _ := http.NewRequest("POST", "https://api.bill.sh/admin/v1/catalog/skus",
bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+adminToken)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var sku map[string]interface{}
json.NewDecoder(resp.Body).Decode(&sku)
fmt.Println("SKU:", sku["id"])
Note: unit_amount_nanos is the price in pico-units. 1000000 = $0.000001 per request.
Graduated Pricing Example
{
"pricing_model": "Graduated",
"tiers": [
{ "up_to": 1000000, "unit_amount_nanos": "1000000" },
{ "up_to": 10000000, "unit_amount_nanos": "750000" },
{ "up_to": null, "unit_amount_nanos": "500000" }
]
}
First 1M requests at $0.000001, next 9M at $0.00000075, everything after at $0.0000005.