Flow · 14 steps

Procure to pay

Buying stock and paying for it: raise a purchase order, authorise it, receive the goods, then settle the supplier invoice at the price actually charged. The interesting part is that these are three services and one story — purchasing owns the order, inventory owns the units, accounting owns what they cost — and no step tells the next what to do beyond issuing its own command.

Services involved

A filled dot is a read; a ring is a call that changes something.

  • Inventory Manager: https://inventory.getmanifold.io
  • Purchasing Manager: https://purchasing.getmanifold.io
  • Accounting Ledger: https://accounting.getmanifold.io

Every call carries Authorization: Bearer $MANIFOLD_TOKEN unless its step says otherwise (getting a token). Run a flow in your sandbox, not your live organization: see Before you start.

1 Create the item

A SKU has to exist before anything can be ordered against it. Untracked stock may be created with an opening balance; serial- or batch-tracked stock must start at zero and arrive through receiving, so every tracked unit has a document behind it.

POST /items Inventory Manager

Request · application/json
{
  "sku": "WIDGET-BLUE",
  "name": "Blue widget, 40mm",
  "quantity": 0
}
Response · 201
{
  "id": "itm_01a15891-6c3f-7d19-9a3e-7c1b5d8e2f10",
  "sku": "WIDGET-BLUE",
  "name": "Blue widget, 40mm",
  "stock": [],
  "createdAt": "2026-08-17T09:00:00.000Z",
  "updatedAt": "2026-08-17T09:00:00.000Z"
}

Records inventory.item-created.

2 Record what the supplier charges

A supplier's price list is one row per SKU: the unit cost, its currency and the supplier's own code. Setting the same row again appends nothing.

PUT /supplier-prices/cmp_01a15884-6399-7e5f-8a6b-1c2d3e4f5a6b/WIDGET-BLUE Purchasing Manager

Request · application/json
{
  "unitCostCents": 1250,
  "currency": "AUD",
  "supplierSku": "NW-40-BLU"
}

What the answer holds: the operation's example.

Records purchasing.supplier-price-set.

3 Read the supplier's price list

Every SKU the supplier prices, by SKU; sku narrows to the ones a buyer is ordering.

GET /supplier-prices?supplierId=cmp_01a15884-6399-7e5f-8a6b-1c2d3e4f5a6b Purchasing Manager

What the answer holds: the operation's example.

4 Raise the purchase order

One line per SKU, with the price you expect to pay — here left to the supplier's price list, which costs the line at 12.50. The order is a draft: nothing is committed to inventory and nothing is owed yet.

POST /pos Purchasing Manager

Request · application/json
{
  "reference": "PO-WIDGET-BLUE",
  "supplier": "Northwind Components",
  "supplierId": "cmp_01a15884-6399-7e5f-8a6b-1c2d3e4f5a6b",
  "lines": [
    {
      "sku": "WIDGET-BLUE",
      "quantity": 40
    }
  ]
}
Response · 201
{
  "id": "po_01a15876-6c9e-7b3a-8c4d-9e0f1a2b3c4d",
  "reference": "PO-WIDGET-BLUE",
  "supplier": "Northwind Components",
  "currency": "AUD",
  "number": "PO-00001",
  "serial": 1,
  "status": "draft",
  "lines": [
    {
      "sku": "WIDGET-BLUE",
      "ordered": 40,
      "received": 0,
      "unitCostCents": 1250
    }
  ],
  "createdAt": "2026-08-17T09:00:00.000Z",
  "updatedAt": "2026-08-17T09:00:00.000Z"
}

From your answer, later steps send id (here po_01a15876-6c9e-7b3a-8c4d-9e0f1a2b3c4d) as {poId}.

Records purchasing.po-created.

5 Authorise it

Committing to the purchase. Purchasing tells the inventory service to expect the units, which is what puts them on the SKU's incoming list — so availability can account for stock that is on its way before it lands.

POST /pos/po_01a15876-6c9e-7b3a-8c4d-9e0f1a2b3c4d/authorise Purchasing Manager

What the answer holds: the operation's example.

Records purchasing.po-authorised, inventory.incoming-expected.

6 Say when it is due

The supplier confirms a delivery day. It is planning, not a change to what was ordered, so it has its own command and fact and leaves inventory's expectations alone — the dashboard measures arriving and overdue orders against it.

PUT /pos/po_01a15876-6c9e-7b3a-8c4d-9e0f1a2b3c4d/expected Purchasing Manager

Request · application/json
{
  "expectedOn": "2026-10-01"
}

What the answer holds: the operation's example.

Records purchasing.po-expected-changed.

7 Receive the goods

The units physically arrive. Purchasing records the receipt and commands inventory to move the quantity into on-hand, passing the receipt id as the idempotency key — so a retried receipt is acknowledged rather than counted twice.

POST /pos/po_01a15876-6c9e-7b3a-8c4d-9e0f1a2b3c4d/receipts Purchasing Manager

Request · application/json
{
  "receiptId": "rcpt_01a15864-bc4f-7f80-b1c3-5d6e7f809a1b",
  "lines": [
    {
      "sku": "WIDGET-BLUE",
      "quantity": 40
    }
  ]
}

What the answer holds: the operation's example.

Records purchasing.receipt-requested, purchasing.receipt-recorded, inventory.stock-received.

8 Settle the supplier invoice

What the stock actually cost, which is rarely exactly what the order said. Accounting revalues the cost layers; units already sold get a cogs-correction entry rather than a rewritten one, because a posted cost is history.

POST /pos/po_01a15876-6c9e-7b3a-8c4d-9e0f1a2b3c4d/invoice Purchasing Manager

Request · application/json
{
  "lines": [
    {
      "sku": "WIDGET-BLUE",
      "unitCostCents": 1310
    }
  ]
}

What the answer holds: the operation's example.

Records purchasing.invoice-authorised.

9 Check the stock landed

Forty units on hand, none reserved, and no incoming left outstanding.

GET /items/WIDGET-BLUE Inventory Manager

What the answer holds: the operation's example.

10 Check what it is worth

The SKU's open cost layers and what the stock on hand is valued at. Note what is not here: no ledger entry. Receiving stock costs money but recognises no expense — the cost sits in a layer until the units are sold, which is when a cogs entry posts. Accounting builds this by consuming the purchasing and inventory event streams, so it settles a moment after the receipt commits rather than inside it.

GET /valuation/WIDGET-BLUE Accounting Ledger

What the answer holds: the operation's example.

11 Check the replica is keeping up

The valuation above was folded from local mirrors of the three source journals, fed by logical replication — so the last question is operational: is anything stalled? Each slot reports whether a walsender is attached and how much WAL its source is retaining. This read appends nothing; it never fails, either — a stalled slot is a warning in the payload, not an error, because a source quietly retaining WAL for a dead consumer is exactly the failure that must stay visible while everything else still works.

GET /replica/status Accounting Ledger

What the answer holds: the operation's example.

12 Cut the units' journal at one moment

Where inventory-manager's journal stood at a single instant: the last sequence the database had recorded for the organization by then, by the database's clock. Taken at one at across the three services, the heads are a consistent cut — replaying each with upTo gives the story exactly as it stood. A read: it appends nothing.

GET /events/head?at=2026-11-07T12%3A39%3A42.897Z Inventory Manager

What the answer holds: the operation's example.

13 Cut the order's journal at one moment

Where purchasing-manager's journal stood at a single instant: the last sequence the database had recorded for the organization by then, by the database's clock. Taken at one at across the three services, the heads are a consistent cut — replaying each with upTo gives the story exactly as it stood. A read: it appends nothing.

GET /events/head?at=2026-11-07T12%3A39%3A42.897Z Purchasing Manager

What the answer holds: the operation's example.

14 Cut the ledgers' journal at one moment

Where accounting-manager's journal stood at a single instant: the last sequence the database had recorded for the organization by then, by the database's clock. Taken at one at across the three services, the heads are a consistent cut — replaying each with upTo gives the story exactly as it stood. A read: it appends nothing.

GET /events/head?at=2026-11-07T12%3A39%3A42.897Z Accounting Ledger

What the answer holds: the operation's example.

What a recorded fact is: Facts and the journal. Why a repeated step changes nothing: Idempotency and retries.