Flow · 17 steps
Curate a channel catalog
Deciding what one selling view is allowed to see: open a catalog for a channel (an Australian online store, a POS register, a B2B company), include a product in it, price its SKU in the catalog's currency, then read the resolved view the channel consumes. Exposure is opt-in — a product a catalog does not include does not exist for that channel — and a store holding several catalogs merges them, a SKU priced in several taking the lowest price.
Services involved
A filled dot is a read; a ring is a call that changes something.
- Inventory Manager:
https://inventory.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
The SKU master first: catalogs expose products, products wrap SKUs, and a SKU must be an inventory item before the product layer may reference it.
POST /items Inventory Manager
{
"sku": "TOTE-CANVAS",
"name": "Canvas tote",
"quantity": 0
}Answers 201. What the answer holds: the operation's example.
Records inventory.item-created.
2 Create the product
The browsable thing over the SKU. One variant here; options, more variants and composite kits behave identically in a catalog.
POST /products Inventory Manager
{
"title": "Canvas tote",
"variants": [
{
"sku": "TOTE-CANVAS"
}
]
}Answers 201. What the answer holds: the operation's example.
From your answer, later steps send id as {productId}.
Records inventory.product-created.
3 Give the product a shape
The option space is re-declarable after creation: axes and values arrive with every variant's new selection (keyed by SKU) in one command, so the product is never caught between shapes. A variant left out keeps its selection, which must still fit.
PUT /products/{productId}/options Inventory Manager
{
"options": [
{
"name": "Fabric",
"values": [
"Canvas"
]
}
],
"variants": [
{
"sku": "TOTE-CANVAS",
"options": [
{
"name": "Fabric",
"value": "Canvas"
}
]
}
]
}What the answer holds: the operation's example.
Records inventory.product-options-set.
4 Open the AU catalog
A catalog is a channel's whole world: a name the views are assigned by, and the one currency its prices are in. It starts empty — nothing is exposed until a product opts in.
POST /catalogs Inventory Manager
{
"name": "AU TOTE-CANVAS",
"currency": "AUD"
}Answers 201. What the answer holds: the operation's example.
From your answer, later steps send id as {catalogId} and id as {baseCatalogId}.
Records inventory.catalog-created.
5 Include the product
Exposure is explicit. Including a product exposes all its variants to this catalog's views; including it twice acknowledges idempotently.
PUT /catalogs/{catalogId}/products/{productId} Inventory Manager
What the answer holds: the operation's example.
Records inventory.catalog-product-included.
6 Price the SKU
Per-SKU, in the catalog's currency — composite (kit) SKUs price exactly like stocked ones. The product must already be included: a price without its product would be inert, so it is refused instead.
PUT /catalogs/{catalogId}/prices/TOTE-CANVAS Inventory Manager
{
"priceCents": 12300
}What the answer holds: the operation's example.
Records inventory.catalog-price-set.
7 Read the channel's view
What a channel app's catalog sync, the POS register or the B2B portal actually consumes: included, non-archived products with their variants and prices. A view holding several catalogs merges these, assignment order deciding price conflicts.
GET /catalogs/{catalogId}/view Inventory Manager
What the answer holds: the operation's example.
8 Open a US catalog beside it
A second currency is a second catalog. This one will not be priced by hand at all: its prices are about to be derived from the AU catalog.
POST /catalogs Inventory Manager
{
"name": "US TOTE-CANVAS",
"currency": "USD"
}Answers 201. What the answer holds: the operation's example.
From your answer, later steps send id as {catalogId}.
Records inventory.catalog-created.
9 Record this hour's forex rate
Forex lives in the platform as facts: one rate per currency pair per hour-long window, recorded by an operator (or a feed driving this same endpoint). An hour with no recording falls back to the newest window before it; re-recording a window is a journalled correction.
PUT /fx/rates Inventory Manager
{
"base": "AUD",
"quote": "USD",
"rateMicros": 650000,
"windowStart": "2026-11-07T12:00:00.000Z"
}What the answer holds: the operation's example.
Records inventory.fx-rate-recorded.
10 Put the US catalog on auto pricing
One fact configures the whole policy: derive every SKU the AU catalog prices at the recorded hourly AUD→USD rate, add 5 %, and round up to the next …9.99 (a base can as well be the SKUs' cost basis, marked up or held to a margin). Derived prices are served marked auto; a price set by hand stands as an override until removed. A channel that prices itself by its own rules (a store's automatic currency conversion) declares channel instead, and manifold derives nothing for it.
PUT /catalogs/{catalogId}/pricing Inventory Manager
{
"_tag": "auto",
"base": {
"_tag": "catalog",
"catalogId": "{baseCatalogId}"
},
"adjustment": {
"_tag": "markup",
"basisPoints": 500
},
"rounding": [
{
"stepCents": 1000,
"endingCents": 999
}
]
}What the answer holds: the operation's example.
Records inventory.catalog-pricing-configured.
11 Read the recorded windows
The platform's forex table, newest first: what every auto-priced catalog is converting with right now.
GET /fx/rates Inventory Manager
What the answer holds: the operation's example.
12 Set the SKU's cost basis
A cost-plus catalog marks up each SKU's replacement cost: the latest receive that carried a cost, or a figure set by hand — a standard cost, or an import's starting figure — in the home currency. Setting the same figure twice appends nothing.
PUT /items/TOTE-CANVAS/cost Inventory Manager
{
"costCents": 4000
}What the answer holds: the operation's example.
Records inventory.sku-cost-set.
13 Read it back
Where the figure came from (set, receipt, or kit for a composite summed from its parts) and when. Not the FIFO average: valuation stays accounting's.
GET /items/TOTE-CANVAS/cost Inventory Manager
What the answer holds: the operation's example.
14 Open a retail catalog priced from cost
A third catalog, this one derived from cost rather than from another catalog.
POST /catalogs Inventory Manager
{
"name": "Retail TOTE-CANVAS",
"currency": "AUD"
}Answers 201. What the answer holds: the operation's example.
From your answer, later steps send id as {catalogId}.
Records inventory.catalog-created.
15 List the product in it
A cost-plus catalog prices the SKUs it lists that have a cost basis.
PUT /catalogs/{catalogId}/products/{productId} Inventory Manager
What the answer holds: the operation's example.
Records inventory.catalog-product-included.
16 Hold it to a 60 % margin, ending in.99
Cost ÷ (1 − 0.60), rounded up by band: to the next.99 below $100, to the next 9.99 from there. A receipt that changes the cost moves the price on the next read; a price typed by hand overrides.
PUT /catalogs/{catalogId}/pricing Inventory Manager
{
"_tag": "auto",
"base": {
"_tag": "cost"
},
"adjustment": {
"_tag": "margin",
"basisPoints": 6000
},
"rounding": [
{
"fromCents": 0,
"stepCents": 100,
"endingCents": 99
},
{
"fromCents": 10000,
"stepCents": 1000,
"endingCents": 999
}
]
}What the answer holds: the operation's example.
Records inventory.catalog-pricing-configured.
17 Read the derived price
40.00 ÷ 0.4 = 100.00, which lands in the second band: 109.99, served marked auto.
GET /catalogs/{catalogId}/prices Inventory Manager
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.