Skip to content
Docs menu

Reference

REST API

Orders

Create customer orders, record fulfilment and corrections, and add optional delivery tracking.

Buyers place orders in their trade portal. Suppliers handle invoicing. They can optionally offer Stripe-hosted customer payments after confirming the final total. Order requests and trade credit remain available. Use the API to pull orders into your ERP or accounts system and to keep their status up to date.

Order statusesLink to this section#

StatusMeaning
newPlaced
pendingOn hold
acknowledgedSeen and being picked
processedReady for dispatch
dispatchedSent by the supplier
completedSupplier marked complete
cancelledCancelled with a customer-facing reason

Staff may skip intermediate picking stages. Moving backwards or reopening a completed or cancelled order requires an internal correctionReason. Previous history stays. Corrections never alter payment state. Read revision and send expectedRevision with updates to reject stale edits.

Create an order for a customerLink to this section#

POST/api/v1/orders/previewwrite key

Requires orders.create and access to the customer. Send customerAccountId and lines containing productId and integer qty (1 to 1,000,000). Choose each ordinary product once, at most 100 lines. A line may include optional configuration containing the returned measurement and choice keys; different configurations of one product remain separate. Optional lineKey gives each preview line a stable caller identity. An additional additionalDiscountBp needs pricing permission and discountReason. Configured order reads include an immutable configuration snapshot with inputs, specification, version, rounding and charges. The preview returns total, fingerprint, current customer prices and currency. Paused accounts, unavailable products and quantities below the minimum are refused. A prices-only catalogue still accepts an order entered here; buyers cannot send one themselves while that is off.

POST/api/v1/orderswrite key

Send the same customer and lines, expectedTotal from the preview, expectedBasket set to its fingerprint, and a fresh UUID requestKey. Optional poNumber (60 characters) and customer-visible notes (2,000 characters) are supported. Reuse exactly the same request and key after an uncertain response: a successful retry returns the original order rather than creating another. Changed prices require another review. The result is placed with orderId and reference, or prices_changed, empty or unavailable.

Staff act under their own identity. The customer's basket is untouched. Customer email contacts may receive a copy; this does not charge payment. Sales Rep has creation access by default; processing and delivery updates require the separate orders.process permission.

Delivery and trackingLink to this section#

PATCH/api/v1/orders/{id}/deliverywrite key

Requires orders.process. Send deliveryMethod: unspecified, courier, own_delivery or collection. Optional courierName (100 characters), trackingReference (120 characters) and trackingUrl (1,000 characters) are customer-visible. Tracking links must use HTTPS without embedded login credentials. Omitted optional details clear them. Send expectedRevision from the current order. There is one delivery record per order; carrier integrations and split shipments are not included. Blank courier details never imply own delivery.

List ordersLink to this section#

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.

NameTypeRequiredDescription
statusstringnoOne of the statuses above.
customerstringnoCustomer id or account code.
limitintegerno1 to 100. Default 100.
offsetintegernoRows 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
}
FieldTypeDescription
idstringOrder id.
referencestringOrder number buyers see, like SO-000142.
statusstringSee statuses.
totalintegerOrder total as priced (see taxLabel), in minor units of currency.
currencystringFixed when the order was placed.
poNumberstring or nullThe buyer’s purchase order number.
createdAtstringWhen it was placed.
customerstringCustomer name. Get the account code from GET /orders/{id}.

Get one orderLink to this section#

GET/api/v1/orders/{id}

The order with its lines and status history.

NameInTypeRequiredDescription
idpathstringyesOrder 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"
      }
    ]
  }
}
FieldDescription
customerAccountIdThe customer’s id.
placedById of the person who entered it. source distinguishes buyer and staff orders.
notesThe buyer’s delivery or order notes.
cancelReasonWhy it was cancelled, if it was.
linesWhat was ordered at what price. A snapshot: later price changes never alter it. productId is null if the product has since been deleted.
lines[].priceLabelThe rule that set the price, as on Prices.
lines[].priceLabel is Price on requestThe product has no price: unitPrice and lineTotal are 0 and the line isn’t in total. The supplier confirms its price.
eventsStatus history, oldest first. actor is the person’s name, or null.

Update or correct an orderLink to this section#

PATCH/api/v1/orders/{id}write key

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

  • Normal forward moves follow the fulfilment stages above. Other moves require a private correction reason; a repeated current status makes no change.
  • Cancelling needs a note, which is shown to the buyer as the reason.
  • Status changes may email customer contacts, including the public note. A generated email does not prove delivery.
  • The change is recorded in the order’s history against the person who made the key.
NameInTypeRequiredDescription
idpathstringyesOrder id.
statusbodystringyesThe new status.
notebodystringnoUp 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()
StatusCodeWhen
400invalid_requestThe move isn’t allowed, e.g. “An order that is received can’t be marked completed”, or cancelling without a note.
403insufficient_scopeThe key is read-only.
404not_foundNo 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 availableLink to this section#

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.

Order lists also include paymentStatus: null for usual account terms, or the optional Stripe payment status. Fulfilment status is separate and never proves payment. The order detail page is the place to review the final confirmed total and approve payment. The API cannot create or approve a payment.

internalNote (2,000 characters) and correctionReason (1,000 characters) are staff-only history fields. note remains public, including old notes. Send expectedRevision to reject a concurrent edit with an invalid request. Staff API reads include private history; buyer tools and portal reads exclude staff identities and private fields. Cancelling still requires a public reason and withdrawal or full refund of an outstanding payment.