# 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.

## How prices are worked out

The most specific rule wins, and **discounts 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 minus one percentage**, the first that applies:
   1. the customer’s discount for the product’s **category**
   2. the customer’s discount for the product’s **brand**
   3. the customer’s own **account discount**
3. Otherwise the **list price**.

A category rule of 10% and an account discount of 5% give 10% off, not 15%.

The discounted unit price is rounded half up to a whole minor unit. The line total is `unit × qty`, so a line never has fractions of a penny.

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.

## The price object

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,
  "source": "brand",
  "label": "Brand discount −15%"
}
```

| Field        | Type            | Description                                                             |
| ------------ | --------------- | ----------------------------------------------------------------------- |
| `unit`       | integer or null | Price for one unit, in minor units. `null` if the product has no price. |
| `line`       | integer or null | `unit × qty`.                                                           |
| `list`       | integer or null | The list price now.                                                     |
| `discountBp` | integer         | The discount applied, in basis points. `0` for agreed and list prices.  |
| `source`     | string          | Which rule set the price: see below.                                    |
| `label`      | string          | Wording buyers see next to the price.                                   |

| `source`   | Meaning                      | Example `label`          |
| ---------- | ---------------------------- | ------------------------ |
| `fixed`    | The customer’s agreed price  | Agreed price             |
| `category` | Category discount            | Category discount −12.5% |
| `brand`    | Brand discount               | Brand discount −15%      |
| `account`  | The customer’s own discount  | Your discount −5%        |
| `list`     | List price, no discount      | (empty)                  |
| `unpriced` | The product has no price yet | (empty)                  |

## Check a customer price

**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?”.

| Name       | Type    | Required | Description                  |
| ---------- | ------- | -------- | ---------------------------- |
| `customer` | string  | yes      | Customer id or account code. |
| `sku`      | string  | yes      | The SKU, ignoring case.      |
| `qty`      | integer | no       | 1 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": "Brand discount −15%"
  },
  "currency": "GBP"
}
```

| Status | Code               | When                                                |
| ------ | ------------------ | --------------------------------------------------- |
| 400    | `validation_error` | `customer` or `sku` missing, or `qty` out of range. |
| 404    | `not_found`        | No 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=…`](/docs/api/products#get-one-product).

## Prices for many products

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](/docs/api/products#list-or-search-products).

## Changing prices

- **List prices** change through price changes, so customers get notice. Send new prices with [`PUT /products`](/docs/api/products#create-or-update-products); they go into a draft that you publish in the app.
- **Discounts and agreed prices** are set per customer in the app. Read them with [`GET /customers/{id}`](/docs/api/customers#get-one-customer).
- A customer’s **account discount** can be set with [`PUT /customers`](/docs/api/customers#create-or-update-customers) (`discountBp`).
