Fulfillment orders

Updated September 9, 20266 min read

A fulfillment order captures one allocation decision: a subset of an Order's lines, the location those lines ship from, and the destination they're going to. It sits between the Order (what the customer bought) and the Shipment (what actually ships).

An Order can produce one or many fulfillment orders. The split happens whenever the lines diverge on any of three dimensions:

  • Fulfillment location. Different warehouses, stores, or dark stores.
  • Delivery method. DELIVERY (carrier delivery to an address), COLLECTION (customer pickup from a collection point), or DIGITAL (no physical delivery; fulfilled digitally).
  • Destination. Different delivery or collection addresses, even within the same delivery method.

Each fulfillment order then produces one or more Shipments when it's booked.

Why this exists as a distinct object

It would be possible to model directly from Order to Shipment, skipping the middle layer. Carriyo doesn't. Real operations have a step between "customer wants this" and "this is being shipped": the moment when allocation decisions are made and locked.

The fulfillment order is that decision. It has:

  • A reference back to the Order.
  • A subset of the order's lines (the ones for this location).
  • A fulfillment location.
  • A status of its own (open, allocated, processing, fulfilled, closed, cancelled).
  • A reference forward to the Shipment(s) that fulfil it.

Putting allocation in its own object means:

  • Allocation can be revised before booking. Change the fulfillment location, move lines between fulfillment orders.
  • Pre-booking holds work. The fulfillment order can sit in allocated while ops review or while inventory is reserved.
  • Reporting is clean. Fulfillment-side metrics (per-location throughput, allocation accuracy) are held on fulfillment orders, separate from shipment-side metrics.

Delivery method and destination

Each fulfillment order carries a delivery method and a destination. The destination field name depends on the method:

Delivery methodDestination field
DELIVERYdelivery_address: where the carrier delivers the parcel.
COLLECTIONcustomer_collection_address: where the customer picks up the parcel.
DIGITAL(no physical destination; the order is fulfilled digitally)

Two fulfillment orders on the same Order can use the same delivery method with different destinations (for example, two DELIVERY FOs going to two different addresses), or different methods entirely (one DELIVERY, one COLLECTION).

Delivery option and allocation

A fulfillment order can carry a delivery_option, typically the shipping option the customer chose at checkout. You pass it back to Carriyo when the Order is created (see POST /orders). The allocation engine uses this as input when picking a fulfillment location.

When a delivery option has associated fulfillment locations, allocation narrows its search to that subset rather than scanning the full pool of locations with inventory. The customer's choice at checkout therefore constrains where the order can be fulfilled from.

What a fulfillment order produces

What a fulfillment order produces depends on its delivery method and, for collections, on whether the customer is collecting from the fulfillment location itself or from a different location.

Delivery methodScenarioEntities created
DELIVERYStandardOne Shipment (sometimes more, when an FO needs to split across consignments).
COLLECTION (local)Customer collects from the fulfillment location.Customer collection only; the parcel doesn't move.
COLLECTION (remote)Customer collects from a different location.Shipment (fulfillment location → collection point) plus customer collection (handover at the collection point).
DIGITALAnyNo Shipment or customer collection; the fulfillment is digital.

Customer collections are created automatically as part of the fulfillment process when the client uses the Fulfillment App. For the Collection lifecycle (ready → OTP → collected, plus expiry, unexpire, and cancel), see Customer collection.

A Shipment, when one is produced, inherits:

  • The fulfillment location (as the Shipment's pickup location).
  • The lines (as items on the shipment).
  • The destination: delivery_address for DELIVERY, or customer_collection_address (the collection point) for a remote COLLECTION.
  • The merchant.

The Shipment has data the fulfillment order doesn't: tracking number, label, carrier-specific references. Those come from the booking call, which is what turns a fulfillment order into a Shipment.

Status flow

A fulfillment order's status is computed from the statuses of its line items.

open → allocated → processing → fulfilled → closed
                                              ↘ cancelled (alternative terminal)
StatusMeaning
openNo fulfillment location assigned yet.
allocatedFulfillment location has been assigned.
processingAt least one line item is in pick / pack / fulfilled.
fulfilledAll line items reached a terminal state, with at least one fulfilled.
closedAll line items are closed (typically once the Shipment is delivered).
cancelledAll line items were cancelled.

For the full criteria see Fulfillment order status codes; the leaf state that drives this is documented in Line item status codes.

Pick and pack

Inside Carriyo's Fulfillment App, the journey from allocated to fulfilled runs through two intermediate workflows:

  • Pick. Staff retrieve the line items from inventory at the fulfillment location.
  • Pack. Picked items are assembled into one or more packages and prepared for handover.

Both workflows produce their own line-item states (pick_in_progress, picked, pack_in_progress) before the line item reaches fulfilled.

These workflows are internal to the Fulfillment App. If you don't use the app (for example, your warehouse runs on a WMS), you bypass them by recording fulfillment directly via POST .../fulfill. Line items move straight from allocated to fulfilled and the Pick and Pack states are skipped.

For the full set of intermediate states, see Line item status codes.

Fulfill, ship, draft, or skip shipping

Recording fulfillment has four shapes. Which one you want depends on what has physically happened.

  • Fulfill. The default. Use it once the warehouse has packed the items. Carriyo marks them fulfilled and creates the shipment.
  • Ship. Use it to retry after fulfillment couldn't create a shipment, to ship items fulfilled with skip_shipping, or to add another shipment. Line-item statuses don't change.
  • create_draft_shipment. The shipment is created but left unconfirmed, so someone can review it before it goes to the carrier.
  • skip_shipping. Fulfills with no shipment at all. The items stay fulfilled rather than closing, so you can ship them later or close them yourself.

A fulfillment order requires shipping when its delivery method is DELIVERY, or COLLECTION from a location other than the one fulfilling it. Local collection, where the customer picks up at the fulfillment location, requires none, and its items close automatically unless the order came from Shopify.

How the payment splits across shipments

Fulfilling a whole order in one call can produce several shipments, and the order's payment has to be divided between them. Carriyo splits both the order total and the amount to collect on delivery by the value of the items each shipment carries. When the items have no prices, it splits by quantity. When neither is available, it splits evenly. The last shipment takes the rounding remainder, so the shares always add back up to the order amounts.

An order with no payment of its own gets no split. Each shipment falls back to the order-level shipment payment instead.

How it fits with other modules

  • Orders. Fulfillment orders are the fulfillment-side child of Orders.
  • Shipping. Fulfillment orders produce Shipments at booking time.
  • Inventory. Allocation decisions often depend on inventory at the chosen location.