# Orders

> Read orders with their lines and history, and move them on from new to acknowledged, dispatched and completed.

Buyers place orders in their trade portal. TradeCatalog doesn’t take payment: you invoice as usual. Use the API to pull orders into your ERP or accounts system and to keep their status up to date.

## Order statuses

| Status         | Meaning                              | Can move to                 |
| -------------- | ------------------------------------ | --------------------------- |
| `new`          | Just placed                          | `acknowledged`, `cancelled` |
| `acknowledged` | You’ve seen it and it’s being picked | `dispatched`, `cancelled`   |
| `dispatched`   | On its way                           | `completed`                 |
| `completed`    | Done                                 | nothing                     |
| `cancelled`    | Cancelled, with a reason             | nothing                     |

## List orders

**GET** `/api/v1/orders`

Orders newest first. The list covers the **200 most recent** orders that match your filters. To pick up new orders, poll with `status=new`.

| Name       | Type    | Required | Description                  |
| ---------- | ------- | -------- | ---------------------------- |
| `status`   | string  | no       | One of the statuses above.   |
| `customer` | string  | no       | Customer id or account code. |
| `limit`    | integer | no       | 1 to 100. Default 100.       |
| `offset`   | integer | no       | Rows to skip. Default 0.     |

```bash
curl "https://tradecatalog.app/api/v1/orders?status=new" \
  -H "Authorization: Bearer $TRADECATALOG_API_KEY"
```

```js
const res = await fetch('https://tradecatalog.app/api/v1/orders?status=new', {
  headers: { Authorization: `Bearer ${process.env.TRADECATALOG_API_KEY}` },
})
const { data: orders } = await res.json()
```

```json
{
  "data": [
    {
      "id": "5b6c7d8e-9f0a-4b1c-8d2e-3f4a5b6c7d8e",
      "reference": "SO-000142",
      "status": "new",
      "total": 31460,
      "currency": "GBP",
      "poNumber": "PO-88231",
      "createdAt": "2026-09-27T07:48:12.000Z",
      "customer": "Oakfield Joinery"
    }
  ],
  "nextOffset": null
}
```

| Field       | Type           | Description                                                  |
| ----------- | -------------- | ------------------------------------------------------------ |
| `id`        | string         | Order id.                                                    |
| `reference` | string         | Order number buyers see, like `SO-000142`.                   |
| `status`    | string         | See [statuses](#order-statuses).                             |
| `total`     | integer        | Order total before tax, in minor units of `currency`.        |
| `currency`  | string         | Fixed when the order was placed.                             |
| `poNumber`  | string or null | The buyer’s purchase order number.                           |
| `createdAt` | string         | When it was placed.                                          |
| `customer`  | string         | Customer name. Get the account code from `GET /orders/{id}`. |

## Get one order

**GET** `/api/v1/orders/{id}`

The order with its lines and status history.

| Name | In   | Type   | Required | Description |
| ---- | ---- | ------ | -------- | ----------- |
| `id` | path | string | yes      | Order id.   |

```bash
curl https://tradecatalog.app/api/v1/orders/5b6c7d8e-9f0a-4b1c-8d2e-3f4a5b6c7d8e \
  -H "Authorization: Bearer $TRADECATALOG_API_KEY"
```

```js
const res = await fetch(`https://tradecatalog.app/api/v1/orders/${orderId}`, {
  headers: { Authorization: `Bearer ${process.env.TRADECATALOG_API_KEY}` },
})
const { data: order } = await res.json()
```

```json
{
  "data": {
    "id": "5b6c7d8e-9f0a-4b1c-8d2e-3f4a5b6c7d8e",
    "customerAccountId": "7d2e4f0a-1b3c-4d5e-8f90-a1b2c3d4e5f6",
    "placedBy": "Fq3L0cZ7tN1pW9aYx2Rk",
    "reference": "SO-000142",
    "poNumber": "PO-88231",
    "notes": "Deliver to the Hebden Road site, gate 2.",
    "status": "new",
    "cancelReason": null,
    "currency": "GBP",
    "total": 31460,
    "createdAt": "2026-09-27T07:48:12.000Z",
    "updatedAt": "2026-09-27T07:48:12.000Z",
    "customer": { "name": "Oakfield Joinery", "accountCode": "OAK01" },
    "lines": [
      {
        "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
        "orderId": "5b6c7d8e-9f0a-4b1c-8d2e-3f4a5b6c7d8e",
        "productId": "3f8a2c1e-9b4d-4e6f-a7c8-1d2e3f4a5b6c",
        "sku": "HX-8510",
        "name": "Hex bolt M10 x 50 zinc",
        "unitLabel": "Box of 100",
        "qty": 20,
        "unitPrice": 1573,
        "listPrice": 1850,
        "priceLabel": "Brand discount −15%",
        "lineTotal": 31460
      }
    ],
    "events": [
      {
        "status": "new",
        "note": null,
        "createdAt": "2026-09-27T07:48:12.000Z",
        "actor": "Sam Patel"
      }
    ]
  }
}
```

| Field                | Description                                                                                                                                  |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `customerAccountId`  | The customer’s id.                                                                                                                           |
| `placedBy`           | Id of the buyer who placed it.                                                                                                               |
| `notes`              | The buyer’s delivery or order notes.                                                                                                         |
| `cancelReason`       | Why it was cancelled, if it was.                                                                                                             |
| `lines`              | What was ordered at what price. A snapshot: later price changes never alter it. `productId` is `null` if the product has since been deleted. |
| `lines[].priceLabel` | The rule that set the price, as on [Prices](/docs/api/prices#the-price-object).                                                              |
| `events`             | Status history, oldest first. `actor` is the person’s name, or `null`.                                                                       |

## Move an order on

**PATCH** `/api/v1/orders/{id}`

Changes an order’s status. Returns the updated order in the same shape as `GET /orders/{id}`.

- Only the moves in the [status table](#order-statuses) are allowed. Anything else is `400 invalid_request`.
- Cancelling needs a `note`, which is shown to the buyer as the reason.
- The buyer who placed the order gets an email each time, with the note if you send one.
- The change is recorded in the order’s history against the owner who made the key.

| Name     | In   | Type   | Required | Description                                       |
| -------- | ---- | ------ | -------- | ------------------------------------------------- |
| `id`     | path | string | yes      | Order id.                                         |
| `status` | body | string | yes      | The new status.                                   |
| `note`   | body | string | no       | Up to 1,000 characters. Required when cancelling. |

```bash
curl -X PATCH https://tradecatalog.app/api/v1/orders/5b6c7d8e-9f0a-4b1c-8d2e-3f4a5b6c7d8e \
  -H "Authorization: Bearer $TRADECATALOG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "status": "dispatched", "note": "DPD tracking 1554 2210 0931" }'
```

```js
const res = await fetch(`https://tradecatalog.app/api/v1/orders/${orderId}`, {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${process.env.TRADECATALOG_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ status: 'acknowledged' }),
})
const { data: order } = await res.json()
```

| Status | Code                 | When                                                                                                              |
| ------ | -------------------- | ----------------------------------------------------------------------------------------------------------------- |
| 400    | `invalid_request`    | The move isn’t allowed, e.g. “An order that is received can’t be marked completed”, or cancelling without a note. |
| 403    | `insufficient_scope` | The key is read-only.                                                                                             |
| 404    | `not_found`          | No such order in this workspace.                                                                                  |

Retrying a `PATCH` that already succeeded returns `400 invalid_request`, because the order has moved on. Check the order’s status before treating that as a failure.

## Not available

The API can’t place orders for a buyer, or change an order’s lines, quantities or prices. Buyers place orders in their trade portal; changes to an order are agreed with the customer and invoiced in your own system.
