Guide

Orders and fulfilment

The order lifecycle: statuses, editing line items, backorders, cancellation requests, forwarding and book-back. What each state actually means and which transitions are safe to automate.

·10 min read ·Updated 3 Sep 2026

Orders carry more state than a status field suggests. This guide covers what the states mean, how line items change under you, and which operations are safe to run without a human watching.

Statuses

open · pending · on-hold · completed · cancelled

Filter with ?status=open, or pass several comma separated. An order arriving from a marketplace can land in any of them: a bol.com import can arrive already completed, which is why orders.completed fires on arrival as well as on transition.

Reading an order

GET /orders gives you a page of summaries. GET /orders/get-single takes either order_pk or order_number, and returns the full record with line items, addresses and channel.

Two identifiers appear throughout the order endpoints and they are not interchangeable:

  • order_pk is Stockpilot’s internal primary key. Most endpoints want this.
  • order_number is the human or marketplace facing reference.

The path parameter is sometimes named order_id and sometimes order_pk for the same value. Read the parameter name on each endpoint rather than assuming.

Editing line items

An order is not frozen once created. Four operations change its contents:

OperationEndpoint
Add a productPOST /orders/{order_pk}/items/add
Replace the product on a line, keeping quantity and pricePOST /orders/{order_pk}/items/swap
Change quantity or record a refundPATCH /orders/ordered-items/{item_id}/update
Remove a lineDELETE /orders/{order_id}/items/{item_id}

items/add is not idempotent. A retry adds a second line rather than reconciling with the first. If the call fails ambiguously, re-read the order and check before sending it again. See rate limits and idempotency.

Deleting a line takes book_back, which decides whether the quantity returns to sellable stock. Omitting it is a decision, not a default to ignore: the units either come back into inventory or they do not.

Backorders

When stock will not cover a line, move it to backorder rather than leaving the order stuck:

POST /orders/{order_id}/move-to-backorder
{
  "backorder_items": [
    { "item_id": 123, "quantity": 5 },
    { "item_id": 456, "quantity": 2 }
  ]
}

The response reports what actually moved, per line:

{
  "message": "Successfully moved 2 item(s) to backorder",
  "items_processed": 2,
  "backorder_status": "partial",
  "backorder_results": [
    { "item_id": 123, "quantity_moved": 5, "new_backorder_qty": 5 }
  ]
}

Note backorder_status: "partial". Read the per line results rather than assuming a 200 moved everything you asked for.

Release them with POST /orders/{order_id}/move-from-backorder once stock lands. Neither direction is idempotent, because both apply a delta to a running backorder quantity.

Cancellations

There are two different things here, and conflating them is a common bug.

  • A cancellation request is a marketplace or customer asking. GET /orders/cancellation-requests lists orders with an open request; GET /orders/cancellation-request checks one order.
  • A cancellation is you acting. PUT /orders/cancel-order cancels the order.

A request does not cancel anything on its own. If you are building a queue for warehouse staff, poll the requests endpoint and let a human decide, especially for orders already picked.

Deleting an order

DELETE /orders/{order_id} removes the order entirely, and takes book_back to decide whether every line’s quantity returns to inventory.

Deletion is not cancellation. A cancelled order stays in the record with a cancelled status and remains visible in reporting. A deleted order is gone. Reach for cancel-order unless you specifically need the record removed.

Forwarding

If another system fulfils an order, mark it forwarded so Stockpilot stops pulling it from the marketplace again:

PATCH /orders/{order_id}/update-forwarding
{ "register_order_id": "MP-12345", "source": "api" }

The returned id is prefixed:

{
  "forwarded_order_id": "FWD-MP-12345",
  "message": "Forwarding details updated.",
  "to_forwarded_channel": "api"
}

That FWD- prefix is what prevents duplicate fetching, so store the returned value rather than reconstructing it yourself. Channels also carry a channel_is_forwarded flag, covered in sales channels.

Fulfilment

POST /orders/fulfil records that an order shipped: carrier, tracking code, tracking URL, shipping method and timestamp. It also accepts a list of items, so partial fulfilment is a matter of naming the lines and quantities that actually went out.

Confirm with GET /orders/fulfillment , which returns the tracking details as stored.

Fulfilment and label buying are separate steps. Getting a label does not fulfil the order, and fulfilling does not buy a label. Shipping and labels covers the ordering, and Order to label walks the whole path.

Updating status directly

PATCH /orders/{order_id}/update-status sets the status field. It is an absolute write, so it is safe to retry.

Use it to reflect a decision you have already made, not to drive the pipeline. Setting an order to completed does not fulfil it, ship it, or move any stock.

A Stockpilot engineer talking through an integration with a developer

Stuck on something

Ask a person, not a search box

The API surface is wide, and some of it only makes sense once someone explains why it works that way. If a payload is not doing what you expect, or you are weighing two approaches, say so and we will look at it with you.