Order lifecycle
This page explains how an Order progresses from ingestion to closure. It also shows how Order status rolls up from fulfillment orders and Shipments. All three lifecycles are distinct but connected; knowing how they relate is the foundation of order management.
Status flow
An Order's status is computed from the statuses of its Fulfillment orders. Fulfillment order status is in turn computed from the statuses of its line items. The progression looks like:
open → partially_allocated → allocated → processing → fulfilled → closed
↘ cancelled (alternative terminal)
| Status | Meaning |
|---|---|
open | No fulfillment orders yet, or all FOs are still open. |
partially_allocated | Some FOs are allocated; others are still open. |
allocated | All FOs reached at least allocated. |
processing | At least one FO is in pick / pack / fulfillment. |
fulfilled | All FOs are fulfilled (or in a terminal state). |
closed | All Shipments delivered → line items closed → FOs closed → Order closed. |
cancelled | Every FO was cancelled. |
on_hold | The order is held. This one is set, not computed. See Putting an order on hold. |
For the full criteria, see Order status codes.
How the rollup works
The cascade from leaf to root:
- A Shipment reaches
delivered. - Every line item linked to that Shipment moves to
closed. - The fulfillment order moves through
processing→fulfilled→closedas its line items reach those states. - The Order moves through
allocated→processing→fulfilled→closedas its FOs reach those states.
Cancellations propagate the same way: when every leaf is cancelled the parent flips to cancelled.
See Fulfillment order status codes and Line item status codes for the full criteria at each level.
Order status vs shipment status
A common point of confusion. They're related but answer different questions:
| Status | Question it answers |
|---|---|
| Order status | "How far through fulfillment is this customer purchase?" |
| Shipment status | "Where is this specific parcel right now?" |
An Order can be fulfilled while one of its Shipments is still
in_transit. The Order moves to fulfilled once handover is
complete; delivery (and the cascade to closed) can follow
later.
Allocation timing
When does allocation happen? Two patterns:
- At ingestion. Allocation rules fire as soon as the order lands. Fulfillment orders are created and allocated immediately. Best when inventory and rules are stable enough to commit early.
- On-demand. The Order sits in
openwhile ops or an upstream system decides allocation. Fulfillment orders are produced manually or via a deferred rule run.
Both are valid. Most integrations pick at-ingestion for speed; ops override (re-allocate) when needed.
Updating orders via bulk import
The bulk import endpoint (POST /orders/bulk/import) supports
both creation and update. When a partner_order_reference
already exists and the order is in open, partially_allocated,
or allocated status, the import applies a partial merge —
the same logic as the single-order update endpoint. Orders that
have progressed beyond allocation (processing, fulfilled,
closed, cancelled) are rejected from bulk updates and must
be updated individually.
This makes the bulk import endpoint safe for periodic syncs from external systems: re-importing the same file updates existing orders in early stages without creating duplicates.
Putting an order on hold
on_hold sits outside the progression above. It is a pause, not
a stage. An Order in open, partially_allocated, or
allocated can be held. Every open or allocated fulfillment
order on it then shows on_hold too.
While the hold is active, updating the Order is rejected, and so is every fulfillment order operation except cancel. Picking is blocked too. Cancelling a held Order is allowed, and it releases the hold on the way through.
on_hold_details records the hold: held_by, hold_date, the
optional sla_hours you supply, and sla_timestamp. That
timestamp is hold_date plus sla_hours. Releasing the hold
leaves the same object in place with released_by,
release_date, and how long the hold lasted. Hold the Order
again and the previous hold moves into history, so the record
accumulates rather than being overwritten.
A single fulfillment order can also be held on its own. The Order's status is unchanged in that case, and only that fulfillment order is blocked.
Cancellation semantics
Cancelling the whole Order. Cancellation is applied while
the Order is open, partially_allocated, allocated, or
on_hold. For an Order in processing, fulfilled, or
closed, the request still succeeds. You get a 200, the Order
comes back unchanged, and the webhooks fire anyway. Nothing in
the response says the request was ignored, so check status in
the response rather than the HTTP code. Past that point, cancel
at the fulfillment order or line-item level instead. Cancelling
an Order that is already cancelled is the one case that is
rejected outright.
Cancelling a fulfillment order that has already been
fulfilled. A fulfilled FO must be unfulfilled before it
can be cancelled. Unfulfilling reverses the fulfillment state
and cascades back to Shipments. Any Shipment whose items all
become unfulfilled is automatically cancelled, provided the
Shipment is still in a cancellable state.
Partial cancellation. Cancelling a single line item or
Fulfillment order while leaving the others in place is
supported. The Order continues toward fulfilled / closed
based on the lines that did fulfil.
Audit and diagnostics
Every order exposes one timeline:
GET /orders/{reference}/activity. It returns a paginated
history built from three sources:
- Change logs. Field-level edits, diffed from the order snapshot taken after each write. They answer "what changed and when".
- System logs. API calls and internal events, each with its timing and response code. They answer "what did the integrations actually exchange".
- Notifications. Customer notifications sent against the
order, carried as
request_typeNOTIFICATION.
A change log and the system log that caused it merge into a
single entry. A system log with no change log appears on its
own, without changes. Housekeeping fields that move on every
write, such as update_date, are left out.
The timeline is the first diagnostic stop when an order looks wrong.