Flow · 20 steps

Journal an additional cost on an order

A cost the order incurred that no other command records — a courier to redeliver, a customs broker's fee — journalled by hand on the sales order or the purchase order it belongs to: an amount, a reason and the chart account it is charged to. Each is one balanced posting in the general ledger: the chosen account debited against 2160 Accrued costs, where it sits until its bill is entered in the ledger app. A cost is never edited: a mistake is reversed, and the reversal posts the exact opposite.

Recording or reversing a cost asks financials:write, because it posts to the ledger. Ids written {orderId} and {poId} are read from an earlier answer.

Retrying each step. A cost carries your key (costId, a cost_… you mint) and a retry answers with the order as the first call left it, appending nothing; a reversal is idempotent by state — a cost reverses once.

Services involved

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

  • Inventory Manager: https://inventory.getmanifold.io
  • Fulfillment Manager: https://fulfillment.getmanifold.io
  • Accounting Ledger: https://accounting.getmanifold.io
  • Purchasing Manager: https://purchasing.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 Add an account for freight

A cost is charged to an account of the chart. A fresh chart holds the SKU defaults and the ledger's control accounts, so the business adds the one it books courier and brokerage charges to.

POST /accounts Inventory Manager

Request · application/json
{
  "code": 6300,
  "name": "Freight and handling"
}

Answers 201. What the answer holds: the operation's example.

Records inventory.account-saved.

2 Have something to sell

Ten mugs on hand, so the order below has stock to hold.

POST /items Inventory Manager

Request · application/json
{
  "sku": "MUG-SPECKLED",
  "name": "Speckled stoneware mug",
  "quantity": 10
}

Answers 201. What the answer holds: the operation's example.

Records inventory.item-created.

3 Open the books

The general ledger begins at the opening stocktake: nothing before it posts. Started with the caller's id (see Open the books with a stocktake).

POST /stocktakes Inventory Manager

Request · application/json
{
  "stocktakeId": "stk_01a15ad0-b9e8-7f0a-8b1c-2d3e4f5a6b7c"
}

Answers 201. What the answer holds: the operation's example.

Records inventory.stocktake-started.

4 Count and value the mugs

Ten on the shelf at $9.50 each: every stock SKU gets a cost at the opening.

PUT /stocktakes/stk_01a15ad0-b9e8-7f0a-8b1c-2d3e4f5a6b7c/counts Inventory Manager

Request · application/json
{
  "counts": [
    {
      "sku": "MUG-SPECKLED",
      "counted": 10
    }
  ],
  "costs": [
    {
      "sku": "MUG-SPECKLED",
      "unitCostCents": 950
    }
  ]
}

What the answer holds: the operation's example.

Records inventory.stocktake-counted.

5 Complete it

The books open here; every cost below posts after it.

POST /stocktakes/stk_01a15ad0-b9e8-7f0a-8b1c-2d3e4f5a6b7c/complete Inventory Manager

What the answer holds: the operation's example.

Records inventory.stocktake-completed.

6 Take an order

Four mugs at $24 each. The first delivery attempt will fail, and the courier will charge to go again.

POST /orders Fulfillment Manager

Request · application/json
{
  "reference": "WEB-20417",
  "lines": [
    {
      "sku": "MUG-SPECKLED",
      "quantity": 4,
      "unitPriceCents": 2400
    }
  ],
  "customer": {
    "personId": "psn_01a15ad0-c0f9-7a1b-8c2d-3e4f5a6b7c8d"
  }
}

Answers 201. What the answer holds: the operation's example.

From your answer, later steps send id as {orderId}.

Records fulfillment.order-created, fulfillment.stock-allocated, fulfillment.shipment-created.

7 Try to charge it to accrued costs

2160 is the account every cost is credited to, kept by the ledger itself, so a cost may not be charged to it — nor to any other control account, an archived account or one the chart lacks. Refused 409 AccountNotPostable, and nothing is recorded.

POST /orders/{orderId}/costs Fulfillment Manager

Request · application/json
{
  "costId": "cost_01a15ad0-e9c0-7551-91df-0a1b2c3d4e5f",
  "amountCents": 4500,
  "reason": "Courier to redeliver after a failed first attempt",
  "accountCode": "2160"
}
Refused · 409
{
  "_tag": "AccountNotPostable",
  "accountCode": "2160",
  "reason": "account 2160 is a control account the ledger posts to itself"
}

Records nothing.

8 Journal the redelivery

$45 charged to freight, in the order's currency (the default) at today's rate. The order keeps the cost, who recorded it and the rate it was booked at.

POST /orders/{orderId}/costs Fulfillment Manager

Request · application/json
{
  "costId": "cost_01a15ad0-e9c0-7551-91df-0a1b2c3d4e5f",
  "amountCents": 4500,
  "reason": "Courier to redeliver after a failed first attempt",
  "accountCode": "6300"
}
Response · 201
{
  "id": "ord_01a15ad0-c1a0-7b2c-8d3e-4f5a6b7c8d9e",
  "reference": "WEB-20417",
  "source": {
    "kind": "api"
  },
  "salesRepId": "usr_01a15ad0-a1b2-7c3d-8e4f-5a6b7c8d9e01",
  "commission": {
    "earnedOn": "invoiced",
    "earner": "order-rep",
    "earnedBy": {
      "userId": "usr_01a15ad0-a1b2-7c3d-8e4f-5a6b7c8d9e01",
      "groupId": "cgr_00000000-0000-0000-0000-000000000000",
      "groupName": "default",
      "baseRateBps": 0
    }
  },
  "customer": {
    "personId": "psn_01a15ad0-c0f9-7a1b-8c2d-3e4f5a6b7c8d"
  },
  "party": {
    "personId": "psn_01a15ad0-c0f9-7a1b-8c2d-3e4f5a6b7c8d"
  },
  "catalogIds": [
    "cat_01a15ad0-a2c3-7d4e-8f5a-6b7c8d9e0f12"
  ],
  "currency": "AUD",
  "fx": {
    "currency": "AUD",
    "home": "AUD",
    "rate": 1,
    "rateDate": "2026-10-21",
    "source": "same"
  },
  "convention": "inclusive",
  "shippingTaxBasis": {
    "_tag": "calculated",
    "treatment": "domestic",
    "country": "AU",
    "class": "follows-goods",
    "rateBps": 0
  },
  "number": "SO-00001",
  "serial": 1,
  "status": "open",
  "lines": [
    {
      "id": "lin_01a15ad0-a3d4-7e5f-9a6b-7c8d9e0f1a23",
      "sku": "MUG-SPECKLED",
      "name": "Speckled stoneware mug",
      "ordered": 4,
      "removed": false,
      "kind": "stock",
      "outsideCatalogs": true,
      "discountCents": 0,
      "discountKind": "amount",
      "allocated": 4,
      "picked": 0,
      "packed": 0,
      "dispatched": 0,
      "invoiced": 0,
      "returned": 0,
      "unitPriceCents": 2400,
      "priceOverride": {
        "by": {
          "_tag": "user",
          "id": "usr_01a15ad0-a1b2-7c3d-8e4f-5a6b7c8d9e01"
        },
        "at": "2026-10-21T01:10:33.484Z"
      },
      "inclusive": true,
      "taxBasis": {
        "_tag": "calculated",
        "treatment": "domestic",
        "country": "AU",
        "parts": [
          {
            "class": "standard",
            "rateBps": 0,
            "weight": 1
          }
        ]
      },
      "orderDiscountCents": 0,
      "tax": {
        "inclusive": true,
        "amountCents": 9600,
        "entries": [
          {
            "class": "standard",
            "treatment": "domestic",
            "country": "AU",
            "rateBps": 0,
            "netCents": 9600,
            "taxCents": 0,
            "source": "manifold"
          }
        ],
        "netCents": 9600,
        "taxCents": 0,
        "grossCents": 9600
      },
      "commission": {}
    }
  ],
  "workflow": {
    "paymentTerms": "on-account",
    "pick": "manual",
    "pack": "auto",
    "ship": "manual",
    "invoice": "off",
    "shortfall": "wait",
    "allocation": "single-site-preferred",
    "routing": {
      "order": "rank",
      "decider": "manifold",
      "priority": []
    },
    "routingByCatalog": {},
    "quoteMode": "quotes",
    "requireCustomer": true,
    "marginPolicies": {}
  },
  "paymentTermsBy": "tenant",
  "createdSequence": 0,
  "shipments": [
    {
      "id": "shp_01a15ad0-a4e5-7f6a-8b7c-8d9e0f1a2b34",
      "number": "SH-00001",
      "reference": "auto:ord_01a15ad0-c1a0-7b2c-8d3e-4f5a6b7c8d9e:1",
      "status": "pending",
      "locationId": "loc_01a15ad0-a5f6-7a7b-9c8d-9e0f1a2b3c45",
      "lines": [
        {
          "lineId": "lin_01a15ad0-a3d4-7e5f-9a6b-7c8d9e0f1a23",
          "sku": "MUG-SPECKLED",
          "quantity": 4
        }
      ],
      "cartons": [],
      "createdAt": "2026-10-21T01:10:33.484Z",
      "updatedAt": "2026-10-21T01:10:33.484Z"
    }
  ],
  "invoices": [],
  "refunds": [],
  "payments": [],
  "notes": [],
  "edits": [],
  "consignments": [],
  "links": [],
  "costs": [
    {
      "costId": "cost_01a15ad0-e9c0-7551-91df-0a1b2c3d4e5f",
      "amountCents": 4500,
      "currency": "AUD",
      "fx": {
        "currency": "AUD",
        "home": "AUD",
        "rate": 1,
        "rateDate": "2026-10-21",
        "source": "same"
      },
      "reason": "Courier to redeliver after a failed first attempt",
      "accountCode": "6300",
      "date": "2026-10-21",
      "recordedBy": {
        "_tag": "user",
        "id": "usr_01a15ad0-a1b2-7c3d-8e4f-5a6b7c8d9e01"
      },
      "recordedAt": "2026-10-21T01:10:35.822Z"
    }
  ],
  "marginApprovals": [],
  "money": {
    "subtotalCents": 9600,
    "charges": {
      "shippingCents": 0,
      "shippingDiscountCents": 0,
      "discountCents": 0
    },
    "discountCents": 0,
    "tax": {
      "netCents": 9600,
      "taxCents": 0,
      "grossCents": 9600,
      "breakdown": [
        {
          "class": "standard",
          "treatment": "domestic",
          "country": "AU",
          "rateBps": 0,
          "exempt": false,
          "taxableCents": 9600,
          "taxCents": 0
        }
      ]
    },
    "totalCents": 9600,
    "paidCents": 0,
    "refundedCents": 0,
    "balanceCents": 9600,
    "refundOwedCents": 0,
    "status": "unpaid"
  },
  "discounts": [],
  "shortfalls": [],
  "routing": [
    {
      "lineId": "lin_01a15ad0-a3d4-7e5f-9a6b-7c8d9e0f1a23",
      "locationId": "loc_01a15ad0-a5f6-7a7b-9c8d-9e0f1a2b3c45",
      "routedBy": "rank"
    }
  ],
  "createdAt": "2026-10-21T01:10:33.484Z",
  "updatedAt": "2026-10-21T01:10:35.822Z"
}

Records fulfillment.cost-recorded.

9 Send it again

The answer was lost, so the same request goes again. The costId is the key: the retry is acknowledged with the order as the first call left it, the cost is on it once, and nothing is appended.

POST /orders/{orderId}/costs Fulfillment Manager

Request · application/json
{
  "costId": "cost_01a15ad0-e9c0-7551-91df-0a1b2c3d4e5f",
  "amountCents": 4500,
  "reason": "Courier to redeliver after a failed first attempt",
  "accountCode": "6300"
}

What the answer holds: the operation's example.

Records nothing.

10 The posting

One balanced posting, source order-cost, naming the order: 6300 freight debited $45, 2160 accrued costs credited $45. Accounting reads fulfillment's journal from its replica, so the poll waits for it. A read claims nothing.

GET /postings?orderId=%7BorderId%7D&source=order-cost Accounting Ledger

What the answer holds: the operation's example.

11 What the books hold of the order

The order's costings: only what reached the ledger. Nothing has shipped or been invoiced, so no line has cost of sales or revenue yet; its one posting is the redelivery (postings, the drill-down into GET /postings?orderId=). A read claims nothing.

GET /orders/{orderId}/costings Accounting Ledger

What the answer holds: the operation's example.

12 Raise a purchase order

Forty more mugs from the pottery, at $9.50 each.

POST /pos Purchasing Manager

Request · application/json
{
  "reference": "PO-POTTERY-0918",
  "supplier": "Kiln Street Pottery",
  "lines": [
    {
      "sku": "MUG-SPECKLED",
      "quantity": 40,
      "unitCostCents": 950
    }
  ]
}

Answers 201. What the answer holds: the operation's example.

From your answer, later steps send id as {poId}.

Records purchasing.po-created.

13 Authorise it

A purchase order takes a cost from authorised until it is invoiced; a draft is refused 409 PoNotAuthorised.

POST /pos/{poId}/authorise Purchasing Manager

What the answer holds: the operation's example.

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

14 Journal the broker's fee

$35 for customs clearance, charged to freight. Nothing has arrived yet and the account is not inventory, so it is an expense — with capitalise: true (or on the inventory account) it would be spread over the units received, like a landed cost. Debits 6300, credits 2160.

POST /pos/{poId}/costs Purchasing Manager

Request · application/json
{
  "costId": "cost_01a15ad0-f0d1-7662-82e0-1b2c3d4e5f60",
  "amountCents": 3500,
  "reason": "Customs broker's clearance fee",
  "accountCode": "6300"
}
Response · 201
{
  "id": "po_01a15ad0-c2b1-7c3d-8e4f-5a6b7c8d9e0f",
  "number": "PO-00001",
  "serial": 1,
  "reference": "PO-POTTERY-0918",
  "supplier": "Kiln Street Pottery",
  "currency": "AUD",
  "status": "authorised",
  "authorisedOn": "2026-10-21",
  "lines": [
    {
      "sku": "MUG-SPECKLED",
      "ordered": 40,
      "received": 0,
      "cancelled": 0,
      "unitCostCents": 950
    }
  ],
  "receipts": [],
  "issues": [],
  "shipments": [],
  "landedCosts": [],
  "costs": [
    {
      "costId": "cost_01a15ad0-f0d1-7662-82e0-1b2c3d4e5f60",
      "amountCents": 3500,
      "currency": "AUD",
      "fx": {
        "currency": "AUD",
        "home": "AUD",
        "rate": 1,
        "rateDate": "2026-10-21",
        "source": "same"
      },
      "reason": "Customs broker's clearance fee",
      "accountCode": "6300",
      "date": "2026-10-21",
      "recordedBy": {
        "_tag": "user",
        "id": "usr_01a15ad0-a1b2-7c3d-8e4f-5a6b7c8d9e01"
      },
      "recordedAt": "2026-10-21T01:10:39.501Z",
      "capitalised": false,
      "lines": []
    }
  ],
  "payables": [],
  "payments": [],
  "revision": 0,
  "createdAt": "2026-10-21T01:10:37.821Z",
  "updatedAt": "2026-10-21T01:10:39.501Z"
}

Records purchasing.cost-recorded.

15 Reverse it on the wrong order

A cost is reversed on the order it was journalled on: the sales order has no cost with that id, so it is 404 CostNotFound, and nothing is recorded.

POST /orders/{orderId}/costs/cost_01a15ad0-f0d1-7662-82e0-1b2c3d4e5f60/reverse Fulfillment Manager

Request · application/json
{
  "reason": "Billed to the supplier instead"
}
Refused · 404
{
  "_tag": "CostNotFound",
  "costId": "cost_01a15ad0-f0d1-7662-82e0-1b2c3d4e5f60"
}

Records nothing.

16 Reverse it

The supplier is to pay the broker after all. The cost stays on the order, marked reversed with who did it and why, and the ledger posts its exact opposite: 2160 debited, 6300 credited.

POST /pos/{poId}/costs/cost_01a15ad0-f0d1-7662-82e0-1b2c3d4e5f60/reverse Purchasing Manager

Request · application/json
{
  "reason": "Billed to the supplier instead"
}
Response
{
  "id": "po_01a15ad0-c2b1-7c3d-8e4f-5a6b7c8d9e0f",
  "number": "PO-00001",
  "serial": 1,
  "reference": "PO-POTTERY-0918",
  "supplier": "Kiln Street Pottery",
  "currency": "AUD",
  "status": "authorised",
  "authorisedOn": "2026-10-21",
  "lines": [
    {
      "sku": "MUG-SPECKLED",
      "ordered": 40,
      "received": 0,
      "cancelled": 0,
      "unitCostCents": 950
    }
  ],
  "receipts": [],
  "issues": [],
  "shipments": [],
  "landedCosts": [],
  "costs": [
    {
      "costId": "cost_01a15ad0-f0d1-7662-82e0-1b2c3d4e5f60",
      "amountCents": 3500,
      "currency": "AUD",
      "fx": {
        "currency": "AUD",
        "home": "AUD",
        "rate": 1,
        "rateDate": "2026-10-21",
        "source": "same"
      },
      "reason": "Customs broker's clearance fee",
      "accountCode": "6300",
      "date": "2026-10-21",
      "recordedBy": {
        "_tag": "user",
        "id": "usr_01a15ad0-a1b2-7c3d-8e4f-5a6b7c8d9e01"
      },
      "recordedAt": "2026-10-21T01:10:39.501Z",
      "reversal": {
        "by": {
          "_tag": "user",
          "id": "usr_01a15ad0-a1b2-7c3d-8e4f-5a6b7c8d9e01"
        },
        "at": "2026-10-21T01:10:40.464Z",
        "reason": "Billed to the supplier instead"
      },
      "capitalised": false,
      "lines": []
    }
  ],
  "payables": [],
  "payments": [],
  "revision": 0,
  "createdAt": "2026-10-21T01:10:37.821Z",
  "updatedAt": "2026-10-21T01:10:40.464Z"
}

Records purchasing.cost-reversed.

17 Reverse it again

Idempotent by state: a cost reverses once, so the retry answers with the order and appends nothing.

POST /pos/{poId}/costs/cost_01a15ad0-f0d1-7662-82e0-1b2c3d4e5f60/reverse Purchasing Manager

Request · application/json
{
  "reason": "Billed to the supplier instead"
}

What the answer holds: the operation's example.

Records nothing.

18 What sits in accrued costs

Every posting with a line on 2160, newest first: the broker's fee and its reversal, which net to nothing, and the redelivery, still awaiting the courier's bill.

GET /postings?account=2160 Accounting Ledger

What the answer holds: the operation's example.

19 The trial balance

Every account's movement since the books opened, which balances because every posting does: the opening stock on inventory against opening balances, and freight netting a $45 debit against 2160's $45 credit — the broker's fee and its reversal cancel out.

GET /postings/trial-balance?from=2026-11-06T12%3A39%3A42.900Z&to=2026-11-08T12%3A39%3A42.900Z Accounting Ledger

What the answer holds: the operation's example.

20 Nothing is owed to anyone yet

A cost journalled by hand is not a supplier's bill: it opens no payable, is not in the purchase order's payables, and no supplier payment settles it — the bill is entered in the ledger app against 2160. With nothing invoiced either, the open items are empty.

GET /open-items 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.