Skip to main content
Sign in with your store to see only the help that applies to you.

Purchasing & Receiving API

Admin

Required scope: purchasing:read

These endpoints expose the buying half of the chain: what was ordered from a supplier, what the supplier invoiced, and what actually arrived on the dock. Without them an external dashboard can see the catalogue, the stock and the sales, but not why the stock number moved — so it can never close the loop on a short shipment or a mis-billed case.

Read-only by design

There is no write side. Raising and receiving purchase orders stays in the Brother POS admin, where the approval and stock-posting steps live. Any non-GET request returns 403 Forbidden.


List Purchase Orders​

GET /api/v1/purchase_orders

Returns a paginated list of purchase orders, newest first (by creation time). Every order includes its line items.

Query Parameters​

ParameterTypeDescription
pageintegerPage number (default: 1)
per_pageintegerResults per page (default: 50, max: 100)
statusstringFilter by status — see the status table below. An unrecognised value is ignored.
supplier_idintegerOnly orders from this supplier
updated_sinceISO 8601Only orders changed after this timestamp — the cursor for a reconciliation pull
with_variancebooleanOnly orders with at least one line that came up short, over, or unexpected

An updated_since value that cannot be parsed as a timestamp returns 422 Unprocessable Entity rather than silently ignoring the filter.

Order Statuses​

StatusMeaning
pendingOrder placed, waiting on the supplier
confirmedSupplier confirmed the order
shippedSupplier shipped it
partially_receivedSome lines received, others still outstanding
receivedThe whole order has been received
cancelledOrder was cancelled

Example​

Find every order still carrying a receiving discrepancy:

curl -H "X-API-Key: bpos_..." \
"https://yourstore.brotherpos.ca/api/v1/purchase_orders?with_variance=true&per_page=50"

Response​

{
"data": [
{
"id": 412,
"order_number": "PO-20260810-0003",
"status": "received",
"supplier": { "id": 7, "name": "Prairie Wholesale" },
"subtotal": 1840.0,
"tax_amount": 239.2,
"shipping_cost": 45.0,
"total": 2124.2,
"ordered_at": "2026-08-10T15:04:00.000Z",
"received_at": "2026-08-14T18:22:00.000Z",
"created_at": "2026-08-10T14:58:00.000Z",
"updated_at": "2026-08-14T18:22:00.000Z",
"has_variance": true,
"line_items": [ ... ]
}
],
"pagination": { "current_page": 1, "per_page": 50, "total_count": 8, "total_pages": 1 },
"meta": { "request_id": "...", "timestamp": "2026-08-19T12:00:00Z" }
}
FieldTypeNotes
order_numberstringHuman-readable reference, unique per store
supplierobject or nullnull on orders with no supplier attached
subtotal, tax_amount, shipping_cost, totalnumberOrder-level money, as JSON numbers
ordered_at, received_attimestamp or nullreceived_at is set when the order is marked received
has_variancebooleanTrue if any line is short, over or unexpected
line_itemsarrayAlways present — see below

Get a Purchase Order​

GET /api/v1/purchase_orders/:id

Returns a single order in exactly the same shape, including its line items. The purchase_order.received webhook carries this same shape in its data.


Line Items: The Three-Way Match​

Each line carries three separate quantities, and the differences between them are three different arguments to have with three different people.

QuantityFieldMeaning
Orderedquantity_orderedWhat you asked the supplier for
Invoicedquantity_invoicedWhat the supplier billed you for — null until a supplier document is applied to the order
Receivedquantity_receivedWhat was actually counted in

The variances are reported separately rather than collapsed into one number:

FieldCalculationWhat it tells you
invoice_varianceinvoiced − orderedThe supplier billed a different quantity than you ordered. A purchasing problem. null with no invoice.
receiving_variancereceived − invoicedYou were billed for units that did not turn up (or received units nobody billed). A billing dispute. null with no invoice.
fulfillment_variancereceived − orderedYou did not get what you asked for. A shipping problem. Always present.
variance_value−(receiving variance) × unit_cost_invoiced, falling back to unit_priceThe money at stake. Positive means the supplier owes you. null with no invoice.
Ordered 24, invoiced 24, received 20

invoice_variance is 0.0 — the bill matches the order. receiving_variance is -4.0 and fulfillment_variance is -4.0 — four units are missing, and you were charged for them. At a $12 invoiced cost, variance_value comes back as 48.0: the supplier owes you $48.

Variance Status​

variance_status summarises received-against-ordered:

ValueMeaning
pendingNothing received on this line yet
okReceived exactly what was ordered
shortReceived less than ordered
overReceived more than ordered
unexpectedArrived without being ordered (nothing was ordered on this line)

The with_variance=true filter selects orders holding a short, over or unexpected line. It is built on received-vs-ordered, so it will not pull in a line that is purely an invoice discrepancy — for instance one invoiced for 8 with 0 received still reads pending. To catch billing gaps as well, pull the orders and check invoice_variance per line yourself.

Match Status​

match_status is a different question — not "did the right quantity arrive" but "do we know which product this line is":

ValueMeaning
matchedThe line is linked to a product in your catalogue
unmatchedNothing in the catalogue has been linked yet — staff need to resolve it
create_newFlagged to create a new product when the order is received

Line Item Fields​

{
"id": 9021,
"product_id": 155,
"product_name": "Blue Dream 3.5g",
"sku": "BD-35",
"unit_type": "unit",
"quantity_ordered": 24.0,
"quantity_invoiced": 24.0,
"quantity_received": 20.0,
"unit_price": 12.0,
"unit_cost_invoiced": 12.0,
"line_total": 288.0,
"variance_status": "short",
"invoice_variance": 0.0,
"receiving_variance": -4.0,
"fulfillment_variance": -4.0,
"variance_value": 48.0,
"match_status": "matched",
"supplier_document_id": 88
}
FieldTypeNotes
product_idinteger or nullNull on an unmatched line
product_name, skustringTaken from the order's snapshot of the product when available, so they still read correctly if the catalogue changes later; otherwise the linked product's current values. Fall back to "Unknown Product" / "N/A".
unit_pricenumberThe cost you ordered at
unit_cost_invoicednumber or nullThe cost the supplier actually billed
supplier_document_idinteger or nullThe invoice or packing document this line was matched against

Keeping a Dashboard in Sync​

Subscribe to purchase_order.received so your system refreshes when a delivery lands, and back it with a scheduled pull using updated_since to catch anything the webhook missed. The reasoning is in Recommended Sync Architecture.

curl -H "X-API-Key: bpos_..." \
"https://yourstore.brotherpos.ca/api/v1/purchase_orders?updated_since=2026-08-19T02:00:00Z"

Troubleshooting​

SymptomCause
403 Forbidden, "Insufficient scope. Required: purchasing:read"The key is missing the purchasing:read scope. Scopes cannot be added to an existing key — create a new key under Settings → Developer API with the scope.
403 Forbidden on a POST/PATCHExpected — these endpoints are read-only via API key.
422 with "updated_since must be a valid timestamp"Send an ISO 8601 timestamp, e.g. 2026-08-19T02:00:00Z.
Invoice quantities are all nullNo supplier document has been applied to the order yet, so there is nothing to match against.
status filter returns every orderThe value is not one of the statuses above, so it was ignored.

What's Next?​

  • Inventory — Stock levels and the adjustment ledger the receiving lands in
  • Products — Catalogue reads, including cost behind costs:read
  • Webhooks — The purchase_order.received event