Bulk changes, scheduling and customer price-change emails require Pro. Free can publish Standard-price spreadsheet imports immediately without emails. Existing agreed prices and published scheduled changes continue to apply when Pro ends.
List prices never change silently in TradeCatalog. New prices go into a price change for one price list: a draft that staff review, date and publish. Customers are emailed only when their own price changes. Standard is the list when you don’t name another. One draft never mixes lists.
How price changes are madeLink to this section#
- From the API:
PUT /productswith a newlistPricefor an existing product adds it to a draft and returns the draft’spriceChangeId. - From the app: spreadsheet imports, and bulk changes like “+4% on the Bolts category”.
Publishing, scheduling and cancelling happen in the app, under Price changes. There’s no API for them: a person always checks a price change before customers see it.
StatusesLink to this section#
| Status | Meaning |
|---|---|
draft | Not published. No prices have changed. |
scheduled | Published with a future date. Prices change on that date. |
live | In effect. |
cancelled | Cancelled before it took effect. |
The price change objectLink to this section#
{
"id": "c4d5e6f7-1234-4abc-9def-0123456789ab",
"source": "import",
"title": "API import (Sage stock sync)",
"supplierNote": null,
"status": "draft",
"effectiveAt": null,
"notifyCustomers": true,
"raiseFixedPrices": false,
"priceListId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"priceListName": "Standard",
"createdBy": "Fq3L0cZ7tN1pW9aYx2Rk",
"publishedAt": null,
"cancelledAt": null,
"createdAt": "2026-09-27T02:00:14.000Z",
"items": 38
}
| Field | Type | Description |
|---|---|---|
source | string | import (spreadsheet or API) or bulk (a percentage change made in the app). |
title | string | Shown to staff. API drafts are titled “API import (key name)”. |
supplierNote | string or null | Message to customers in the price-change email. |
status | string | See statuses. |
effectiveAt | string or null | When the new prices apply. |
notifyCustomers | boolean | Whether customers are emailed their new prices. |
raiseFixedPrices | boolean | Whether agreed prices rise by the same percentage. That choice is separate from the list. |
priceListId | string or null | The list this change writes. Null means Standard, including changes from before lists were named. |
priceListName | string | That list’s name. Standard when priceListId is null. |
createdBy | string or null | Id of the person who made it. For API drafts, the person who made the key. |
items | integer | How many products it changes. Only in the list. |
List price changesLink to this section#
GET/api/v1/price-changes
Newest first. Covers the 100 most recent price changes.
| Name | Type | Required | Description |
|---|---|---|---|
limit | integer | no | 1 to 100. Default 100. |
offset | integer | no | Rows to skip. Default 0. |
curl https://tradecatalog.app/api/v1/price-changes \
-H "Authorization: Bearer $TRADECATALOG_API_KEY"
const res = await fetch('https://tradecatalog.app/api/v1/price-changes', {
headers: { Authorization: `Bearer ${process.env.TRADECATALOG_API_KEY}` },
})
const { data: changes } = await res.json()
const drafts = changes.filter((change) => change.status === 'draft')
Preview a price changeLink to this section#
GET/api/v1/price-changes/{id}
Everything staff see before publishing: how many prices go up and down, the biggest moves, warnings, and how many agreed prices it touches. Pass customer to see that customer’s own prices before and after.
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes | Price change id. |
customer | query | string | no | Customer id or account code. |
curl "https://tradecatalog.app/api/v1/price-changes/c4d5e6f7-1234-4abc-9def-0123456789ab?customer=OAK01" \
-H "Authorization: Bearer $TRADECATALOG_API_KEY"
const res = await fetch(
`https://tradecatalog.app/api/v1/price-changes/${priceChangeId}?customer=OAK01`,
{ headers: { Authorization: `Bearer ${process.env.TRADECATALOG_API_KEY}` } },
)
const { data: preview } = await res.json()
if (preview.warnings.length)
console.warn('Check these before publishing', preview.warnings)
{
"data": {
"change": {
"id": "c4d5e6f7-1234-4abc-9def-0123456789ab",
"source": "import",
"title": "API import (Sage stock sync)",
"supplierNote": null,
"status": "draft",
"effectiveAt": null,
"notifyCustomers": true,
"raiseFixedPrices": false,
"priceListId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"priceListName": "Standard",
"createdBy": "Fq3L0cZ7tN1pW9aYx2Rk",
"publishedAt": null,
"cancelledAt": null,
"createdAt": "2026-09-27T02:00:14.000Z"
},
"counts": { "total": 38, "up": 36, "down": 2 },
"biggest": [
{
"productId": "3f8a2c1e-9b4d-4e6f-a7c8-1d2e3f4a5b6c",
"oldPrice": 1850,
"newPrice": 1950,
"sku": "HX-8510",
"name": "Hex bolt M10 x 50 zinc",
"changeBp": 541
}
],
"warnings": [],
"fixedAffected": 3,
"customer": {
"name": "Oakfield Joinery",
"rows": [
{
"sku": "HX-8510",
"name": "Hex bolt M10 x 50 zinc",
"before": 1573,
"after": 1658,
"label": "Brand discount −15%"
}
]
}
},
"currency": "GBP"
}
| Field | Description |
|---|---|
counts | Products in the change, and how many go up and down. |
biggest | Up to 20 items with the largest percentage move. changeBp is the change in basis points (541 = +5.41%). |
warnings | Items that move by more than 30% either way, or go to zero. Usually typos. |
fixedAffected | Agreed prices on these products that are valid now. They only change if the change raises agreed prices. |
customer | With customer: that customer’s unit prices before and after, from their own list and then their adjustment, for up to the first 50 items. A customer on another list is unchanged. null without it. |
404 not_found if there’s no such price change in this workspace.