Flow · 13 steps
Order to cash
Sell stock and get paid: quote, place the order, pick, pack and ship a parcel, take the payment, invoice it and refund part of it.
Each organization chooses which of these steps are manual and which run by themselves. An order keeps the choices made when it was placed, so a later change never alters an order already under way. This walk makes every step manual so you can see each one. With pick and ship set to auto, the same facts are recorded by the one call that places the order.
Run it in a sandbox or a test organization: step 1 changes how every new order is handled. Ids written {orderId} are read from an earlier answer.
Retrying each step. If an answer is lost, send the same request again. The create, payment, invoice and refund carry your key (reference, paymentId, the shipment, refundId) and answer with what the first call recorded. A retried place or ship answers 200 with the order as it stands. A retried pick or pack answers 409 ShipmentNotInState: read the order and check the parcel's shipments[].status — if it is picked or packed, your first call landed.
Services involved
A filled dot is a read; a ring is a call that changes something.
- Fulfillment Manager:
https://fulfillment.getmanifold.io - Inventory Manager:
https://inventory.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 Make every step manual
For a test organization or a sandbox only. This replaces the whole pipeline for every new order: any field left out goes back to its default. To read the pipeline without changing it, use GET /workflow. A step cannot be manual after an automatic one. Payment terms are on-account here, so a parcel may ship before it is paid; under prepaid the ship in step 8 is refused 409 OrderUnpaid until the payment in step 9.
PUT /workflow Fulfillment Manager
{
"pick": "manual",
"pack": "manual",
"ship": "manual",
"invoice": "manual",
"shortfall": "split",
"paymentTerms": "on-account"
}{
"paymentTerms": "on-account",
"pick": "manual",
"pack": "manual",
"ship": "manual",
"invoice": "manual",
"shortfall": "split"
}Records fulfillment.workflow-configured.
2 Have something to sell
Forty units on hand. Stock usually arrives through purchasing (see Procure to pay), but the SKU must exist and hold units before an order can hold any.
POST /items Inventory Manager
{
"sku": "WIDGET-BLUE",
"name": "Blue widget, 40mm",
"quantity": 40
}Answers 201. What the answer holds: the operation's example.
Records inventory.item-created.
3 Quote it
A draft quote: one line per item. Leave out unitPriceCents and the line takes its price from a catalog; send it, as here, for a SKU no catalog lists yet, or a quote cannot be issued. Each line gets a lin_ id, which later steps name. Its prices can still change (PUT /orders/{orderId}/lines/{lineId}/price). A quote holds no stock and is not a sale. It names no customer here: an order needs none to ship, be paid or be invoiced. An order made directly (stage: "order", the default) skips this step and the next.
POST /orders Fulfillment Manager
{
"reference": "WEB-10231",
"stage": "draft-quote",
"lines": [
{
"sku": "WIDGET-BLUE",
"quantity": 6,
"unitPriceCents": 2400
}
],
"customer": {
"personId": "psn_01a15890-1b2c-7d3e-8f4a-5b6c7d8e9f0a"
}
}Answers 201. What the answer holds: the operation's example.
From your answer, later steps send id as {orderId} and lines[0].id as {lineId}.
Records fulfillment.quote-created.
4 Issue the quote
Sent to the customer. Its prices are now fixed: a reprice or an edit is refused until the quote is reopened. It still holds no stock.
POST /orders/{orderId}/quote Fulfillment Manager
{}What the answer holds: the operation's example.
Records fulfillment.quote-issued.
5 Place it, which holds the stock and opens a parcel
The customer accepted. The order is the quote as it stood, under the same number. Placing it holds the stock and opens a parcel over what it held, so there is a shp_ id to pick against with no further call. POST /orders/{orderId}/shipments opens another parcel, for units held later when the order was short. Placing twice never holds stock twice. Stock is held at the organization's locations by its routing rules; Running more than one location shows how a site is chosen.
POST /orders/{orderId}/place Fulfillment Manager
What the answer holds: the operation's example.
From your answer, later steps send shipments[0].id as {shipmentId}.
Records fulfillment.order-created, fulfillment.stock-allocated, fulfillment.shipment-created.
6 See it on the operations board
The warehouse floor as the ops view shows it: each site's parcels by stage, the longest standing first. The new parcel waits under pending to be picked; nothing is held for payment, refused by a carrier or stuck yet.
GET /orders/board?today=2026-08-17T00%3A00%3A00.000Z&stuckHours=24 Fulfillment Manager
{
"asOf": "2026-08-17T09:00:00.000Z",
"today": "2026-08-17T00:00:00.000Z",
"sites": [
{
"locationId": "loc_01a15852-46d1-7a3b-9c0d-1e2f3a4b5c6d",
"pending": {
"count": 1,
"oldest": [
{
"orderId": "ord_01a15859-9053-7a55-9e77-0b2c4d6e8f01",
"orderNumber": "SO-00042",
"shipmentId": "shp_01a15888-1ced-7bb0-8f21-3a5c7e9d1b04",
"number": "SH-00042",
"since": "2026-08-17T09:00:00.000Z"
}
]
},
"picked": {
"count": 0,
"oldest": []
},
"packed": {
"count": 0,
"oldest": []
},
"shippedToday": 0,
"shippedYesterday": 0,
"awaitingPayment": {
"count": 0,
"orders": []
},
"refusedConsignments": {
"count": 0,
"items": []
},
"stuck": {
"count": 0,
"orders": []
}
}
],
"awaitingPayment": {
"count": 0,
"orders": []
},
"refusedConsignments": {
"count": 0,
"items": []
},
"stuck": {
"count": 0,
"orders": []
},
"stuckHours": 24
}7 Pick it
What the picker took off the shelf. A tracked SKU names its serials or its batch, and those units are taken off the free shelf at once, so no other parcel can claim them. A refused pick records nothing.
POST /orders/{orderId}/shipments/{shipmentId}/pick Fulfillment Manager
{
"lines": [
{
"lineId": "{lineId}",
"quantity": 6
}
]
}What the answer holds: the operation's example.
Records fulfillment.shipment-picked.
8 Pack it
The parcel is closed, with the carton it went in, weighed and measured for the carrier. Stock does not move yet.
POST /orders/{orderId}/shipments/{shipmentId}/pack Fulfillment Manager
{
"cartons": [
{
"packaging": "carton",
"weightGrams": 2400,
"size": {
"lengthMm": 300,
"widthMm": 200,
"heightMm": 150
}
}
]
}What the answer holds: the operation's example.
Records fulfillment.shipment-packed.
9 Ship it
Stock leaves here, and its cost becomes the cost of the sale. Each line's held stock is taken from inventory, one fulfillment.dispatched per line. Sending the ship again changes nothing: the parcel ships once.
POST /orders/{orderId}/shipments/{shipmentId}/ship Fulfillment Manager
{
"carrier": "DHL",
"trackingNumber": "JD0002001234567"
}What the answer holds: the operation's example.
Records fulfillment.dispatched, fulfillment.shipment-shipped.
10 Take the money
The customer pays. Pay the order's money.balanceCents, read from GET /orders/{orderId}: here 6 × 24.00 = 144.00, as this walk's organization has no tax rates. With tax set up, the balance includes it, and paying less leaves the order partially-paid. Paid in full, money goes to paid and the balance to zero. A payment over the balance is refused. You make the pay_ id, so a retry counts the money once. Under prepaid terms, this is what lets a packed parcel ship.
POST /orders/{orderId}/payments Fulfillment Manager
{
"paymentId": "pay_01a15863-cdfa-7c8d-9e0f-1a2b3c4d5e6f",
"amountCents": 14400,
"method": "card",
"reference": "ch_3Nq7Xy2eZvKYlo2C1abcdefg"
}Answers 201. What the answer holds: the operation's example.
Records fulfillment.payment-recorded.
11 Invoice it
A sales invoice for the parcel that shipped. Naming the shipment is what makes a retry safe: it is answered with the invoice already raised. Sent with no shipmentId, the call bills everything shipped and not yet billed, and a retry finds nothing left: 409 NothingToInvoice. What was paid is on the order's money.
POST /orders/{orderId}/invoices Fulfillment Manager
{
"shipmentId": "{shipmentId}"
}Answers 201. What the answer holds: the operation's example.
Records fulfillment.invoice-issued.
12 Refund part of it
Money back. A line marked restock comes back into stock: sellable units go back on the shelf, and their cost comes off the cost of sales. A damaged return is written off at the same cost instead.
POST /orders/{orderId}/refunds Fulfillment Manager
{
"refundId": "rfd_01a15887-2e98-7f3a-8c6d-1e2f3a4b5c6d",
"amountCents": 4800,
"lines": [
{
"lineId": "{lineId}",
"quantity": 2,
"restock": true,
"condition": "sellable"
}
]
}Answers 201. What the answer holds: the operation's example.
Records fulfillment.refunded.
13 Read the resulting cost of sales
Six units sold and two returned. The ledger shows both entries; the sale is never rewritten.
GET /ledger?sku=WIDGET-BLUE 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.