GuidesFulfillment & inventory

Fulfillment and inventory

Goal: bring stock in against a purchase order and receive it into a lot, and route an order line to a dropship vendor — all through the fdk_ merchant API.

Audience: a back-office integration authenticating with an fdk_ credential (Authorization: Bearer fdk_…). The tenant is carried by the credential. Money is integer cents.

The supply-and-routing surface splits by ownership: vendors, purchase orders, and receiving are inventory (scope manage:own_inventory); fulfillment nodes and dropship dispatch are fulfillment (scope manage:own_fulfillment).

Bring stock in: vendor, PO, receive

Create a vendor

POST /v1/merchant/inventory/vendors HTTP/1.1
Host: api.ruo.pro
Authorization: Bearer fdk_live_…
Content-Type: application/json
 
{ "name": "Nova Peptide Supply" }

201 returns { vendor: { id, tenant_ref, name } }.

Create a purchase order with its lines

Each line references a canonical_item_id, an expected_qty, and a landed unit_cost_cents (integer cents).

POST /v1/merchant/inventory/purchase-orders HTTP/1.1
Authorization: Bearer fdk_live_…
Content-Type: application/json
 
{
  "vendor_id": "ven_…",
  "reference": "PO-2026-014",
  "lines": [
    { "canonical_item_id": "item_bpc5mg", "expected_qty": 200, "unit_cost_cents": 1200 }
  ]
}

201 returns { purchase_order, lines }. A PO’s status is open, partially_received, received, or cancelled.

Receive stock against a line into a lot

Receiving creates an inventory lot — the traceable unit that carries manufacturing/expiry dates and drives FEFO reservations.

POST /v1/merchant/inventory/purchase-orders/{id}/receive HTTP/1.1
Authorization: Bearer fdk_live_…
Content-Type: application/json
 
{
  "line_id": "pol_…",
  "internal_lot": "LOT-BPC-2026-014",
  "qty_received": 200,
  "vendor_lot": "NP-88213",
  "mfg_date": "2026-08-01",
  "exp_date": "2028-08-01",
  "location_id": "loc_…"
}

201 returns { lot, line, purchase_order } — the created lot, the line with its updated received_qty, and the PO with its walked status.

⚠️

Receiving fails closed. Receiving against a non-receivable PO (po_not_receivable) or an over-receipt beyond expected_qty (receive_exceeds_expected) is refused — never a silent partial write.

Read what you have

GET /v1/merchant/inventory/vendors                       -> { vendors: [...] }
GET /v1/merchant/inventory/purchase-orders               -> { purchase_orders: [...] }
GET /v1/merchant/inventory/purchase-orders/{id}          -> { purchase_order, lines }

An unknown PO id is a 404 for this tenant.

Fulfillment nodes and dropship routing

A fulfillment node is an endpoint stock ships from — own_warehouse, threepl, or dropship_vendor.

Create a node

POST /v1/merchant/fulfillment/nodes HTTP/1.1
Authorization: Bearer fdk_live_…
Content-Type: application/json
 
{ "kind": "dropship_vendor", "name": "Nova Dropship", "vendor_ref": "ven_…" }

201 returns { node: { id, tenant_ref, kind, name, location, vendor_ref } }. List them with GET /v1/merchant/fulfillment/nodes.

Route a queued order to the dropship vendor

Dropship routes each line to a vendor node without reserving or decrementing your own stock — it records a per-line vendor dispatch. Supply a fallback unit_cost_cents per line for COGS when no vendor-cost mapping exists.

POST /v1/merchant/fulfillment/{id}/dropship HTTP/1.1
Authorization: Bearer fdk_live_…
Content-Type: application/json
 
{
  "node_id": "node_…",
  "lines": [ { "queue_item_id": "qi_…", "unit_cost_cents": 1500 } ]
}

201 returns { queue, dispatches } — the advanced queue row and the recorded dropship dispatches (each dispatched or shipped, with a resolved unit_cost_cents).

The queue row is created automatically when an order is placed — the fulfillment module consumes commerce.order.placed.v1 and enqueues the row and its lines. You route an existing queue row; you do not create it.

Refusals

  • 404 — the queue row or node does not exist for this tenant.
  • 409 — the queue row is not in a state that permits dropship routing (for example it is already allocated to own stock — mixing modes on one route is refused).

The full pick/pack/ship depth

Own-stock allocation, buying carrier labels, the ship transition (which commits reservations, captures COGS, and walks the commerce order), and marking delivered are covered — with their operator and merchant routes, modes (dedicated / allocated / dropship), and COGS capture — in the reference.