Skip to content
Docs menu

Reference

REST API

Prices

What a customer pays for a SKU at a quantity, and exactly which rule set that price.

TradeCatalog works out every customer price in one place, the same way for the app, the trade portal, MCP and the API. You never calculate prices yourself.

Configured products, measured rates, quantity thresholds, net extras and explicit rounding also use this pricing service. Quote a complete line with POST /products/{id}/quote; a missing price may mean configurationRequired, while a valid unpriced specification is Price on request. Use the returned line total. See trade catalogue setup for precedence, net treatment and publication.

How prices are worked outLink to this section#

The most specific rule wins, and adjustments never stack:

  1. Agreed price. If the customer has an agreed (fixed) price for the product, that’s the price.
  2. Otherwise the list price with one percentage, the first that applies. A percentage is a discount (taken off) or a markup (added on):
    1. the customer’s percentage for that product
    2. the customer’s percentage for the product’s category (the nearest parent wins)
    3. the customer’s percentage for the product’s brand
    4. the customer’s own percentage on the whole list
  3. Otherwise the list price.

A category discount of 10% and a whole-list markup of 5% give 10% off, not both. The unit is never below zero, and it is rounded half up to a whole minor unit. The line total is unit × qty.

List prices and agreed prices are versioned by date. The API always gives the price valid now. Future prices from a scheduled price change apply from their date.

Price lists and catalogue accessLink to this section#

Standard is the supplier's master catalogue. Other price lists can contain products with prices and products awaiting a quote. A missing price on an assigned list does not fall back to Standard.

In the workspace, open Price lists and choose a named list. Assigned customers can see has two choices:

  • All permitted catalogue products is the default. The list sets prices; assigned buyers can also browse other permitted products as Price on request.
  • Only products on this list limits every assigned buyer's catalogue, searches, product links, downloads and new orders to that list's products. Other product visibility and approved-list restrictions still apply. Customers on other lists keep their existing access.

Each list shows its customer count, priced products, unpriced products and total products. Totals include inactive products, so they can exceed what a buyer currently sees. A zero price counts as priced. A future price counts as unpriced until its start date.

To add products, select them on Products, choose Add to price list, then copy Standard prices with an optional percentage or select Add without prices. Existing members keep their prices. Products without a Standard price are added as Price on request. On a named list's page, saving a SKU with a blank price adds it without a price or clears its current price. Remove takes it off that list while keeping the master product and past orders. Resolve any pending price change for that product first.

These controls require Pro and global price-edit rights. Existing lists keep their catalogue access until a supplier changes it. API imports into a named list include ordinary product rows with no price as unpriced members; an empty cell in a prices-only import leaves access unchanged.

The price objectLink to this section#

Every product’s price has this shape. GET /prices returns the same fields at the top level of data.

json
{
  "unit": 1573,
  "line": 15730,
  "list": 1850,
  "discountBp": 1500,
  "adjustment": "discount",
  "source": "brand",
  "label": "15% off your list",
  "remark": null
}
FieldTypeDescription
unitinteger or nullPrice for one unit, in minor units. null if the product has no price.
lineinteger or nullunit × qty.
listinteger or nullThe list price now.
discountBpintegerThe adjustment applied, in basis points. 0 for agreed and list prices.
adjustmentdiscount or markupWhich way discountBp moves the list price. discount when there is no percentage.
sourcestringWhich rule set the price: see below.
labelstringWording buyers see next to the price.
remarkstring or nullThe agreed-price note when that customer is allowed to see it. Otherwise null. The staff-only internal note is never included.
sourceMeaningExample label
fixedThe customer’s agreed priceAgreed price
productPercentage on one product10% off your list
categoryPercentage on a category12.5% off your list
brandPercentage on a brand15% above your list
accountPercentage on the whole list10% above your list
listList price, no adjustment(empty)
unpricedThe product has no price yet(empty)

Check a customer priceLink to this section#

GET/api/v1/prices

What a customer pays for one SKU at a quantity, and why. Use it to answer “what does Oakfield pay for 10 boxes of HX-8510?”.

NameTypeRequiredDescription
customerstringyesCustomer id or account code.
skustringyesThe SKU, ignoring case.
qtyintegerno1 to 1,000,000. Default 1.
bash
curl "https://tradecatalog.app/api/v1/prices?customer=OAK01&sku=HX-8510&qty=10" \
  -H "Authorization: Bearer $TRADECATALOG_API_KEY"
js
const params = new URLSearchParams({
  customer: 'OAK01',
  sku: 'HX-8510',
  qty: '10',
})
const res = await fetch(`https://tradecatalog.app/api/v1/prices?${params}`, {
  headers: { Authorization: `Bearer ${process.env.TRADECATALOG_API_KEY}` },
})
const { data: price, currency } = await res.json()
json
{
  "data": {
    "customer": {
      "id": "7d2e4f0a-1b3c-4d5e-8f90-a1b2c3d4e5f6",
      "accountCode": "OAK01",
      "name": "Oakfield Joinery"
    },
    "sku": "HX-8510",
    "name": "Hex bolt M10 x 50 zinc",
    "qty": 10,
    "unit": 1573,
    "line": 15730,
    "list": 1850,
    "discountBp": 1500,
    "source": "brand",
    "label": "15% off your list",
    "remark": null
  },
  "currency": "GBP"
}
StatusCodeWhen
400validation_errorcustomer or sku missing, or qty out of range.
404not_foundNo such customer, or no product with that SKU.

This endpoint checks the price only. It doesn’t check whether the customer can see the product. For that, use GET /products/{id}?customer=….

Prices for many productsLink to this section#

To price a whole page of products for one customer, use GET /products?customer=OAK01. Every row’s price is that customer’s price, and customer-only products for other customers are left out. See Products.

Changing pricesLink to this section#

  • List prices change through price changes, so customers get notice. Send new prices with PUT /products; they go into a draft that you publish in the app.
  • Markups, discounts and agreed prices are set per customer in the app. Read them with GET /customers/{id}.
  • Agreed prices for many customers are set with PUT /agreed-prices.
  • A customer’s whole-list percentage can be set with PUT /customers (discountBp, and optional adjustment).

Set agreed pricesLink to this section#

PUT/api/v1/agreed-priceswrite key

Create or replace agreed prices for existing customers and products. Send up to 2,000 rows. Prices are integer minor units. Leave commit false to preview: nothing is saved, and each unknown account or SKU comes back as a row error. Set commit to true to save. Saving closes the agreed price valid now and inserts the new one from this moment, in batches of 100. Pro only.

NameTypeRequiredDescription
commitbooleannotrue saves. Default false previews only.
pricesarrayyes1 to 2,000 prices.
accountCodestringyesCustomer account code, ignoring case.
skustringyesProduct SKU, ignoring case.
priceintegeryesAgreed price in minor units.
notestringnoUp to 200 characters, shown to staff.
bash
curl -X PUT https://tradecatalog.app/api/v1/agreed-prices \
  -H "Authorization: Bearer $TRADECATALOG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"commit":false,"prices":[{"accountCode":"OAK01","sku":"HX-8510","price":1250,"note":"Contract"}]}'
StatusCodeWhen
400validation_errorA price is missing, negative, or over the limit.
403read_onlyThe workspace is not on Pro, or the key is read-only.