Orders and fulfilment

Cart to order to shipment to return on revenexx, the stock those movements consume, the audit feed, the number ranges and the documents filed against an order — five apps, one flow.

Five apps carry this flow, and each owns one stage:

AppBase pathOwns
carts/v1/cartsThe buying workspace, until it becomes an order
orderlists/v1/orderlistsSaved, reusable position collections
orders/v1/ordersThe order, its shipments, cancellations, returns and audit trail
inventories/v1/inventoriesLocations, stock levels, the movements ledger, reservations
documents/v1/documentsThe register of documents filed against an order, and their kinds

The one design rule to internalise

Each route does exactly one thing, and the orchestration is yours. Placing an order does not reserve stock, take payment or talk to an ERP. Shipping does not print a label. Cancelling refunds nothing and returns nothing to stock. Completing a return books the goods on the order and hands you a restock array to pass on.

That is deliberate: an order lifecycle differs per merchant, and an app that fused the steps would be un-composable. What ties the steps together is the event trail — every action writes an order_events row, which is also the domain event a workflow reacts to, so what a workflow saw and what an operator reads cannot diverge.

The happy path

ONE HAND-OFF1 · open a cartPOST /v1/carts2 · add linesPOST /v1/carts/{id}/items3 · close the cart, hand offPOST /v1/carts/{id}/order4 · the order comes into beingPOST /v1/orders/place5 · the fulfilling system took itPOST /v1/orders/{id}/acknowledge6 · hold the stockPOST /v1/inventories/reserve7 · book goods outPOST /v1/orders/{id}/ship8 · consume the reservationPOST /v1/inventories/commit9 · record what the money didPOST /v1/orders/{id}/payment-status10 · close it (or let shipping close it)POST /v1/orders/{id}/completeyour backend: each call, in order

And when goods come back:

text
POST /v1/orders/{id}/return                       register the case
POST /v1/orders/{id}/returns/{rid}/receive        the parcel is here
POST /v1/orders/{id}/returns/{rid}/complete       accept it — books quantity_returned
POST /v1/inventories/restock                      put the sellable goods back
There is no /v1/returns. A return is a sub-resource of an order and has no top-level path. The list across orders is GET /v1/orders/returns (as of 2026-11). See Returns.

Five status dimensions, and they are orthogonal

DimensionValuesWritten by
statuspending, placed, in_fulfillment, completed, cancelledThe action routes
fulfillment_statusunfulfilled, partial, fulfilledDerived from position arithmetic
payment_statusopen, pending, authorized, paid, partially_paid, refunded, failedFed in by whatever took the money
on_hold + hold_reasonbooleanhold / unhold, and deliberately outside the lifecycle
Return statusregistered, received, completed, rejectedThe four return routes

Value lists

The app publishes the words it enforces, so a client can read them instead of keeping its own copy.

  • GET /v1/orders/vocabularies lists the sets by name, with a title and a description and no values.
  • GET /v1/orders/vocabularies/{name} returns one set with every value, its title, its description and a badge tone.

The index names 19 sets: statuses, payment-statuses, fulfillment-statuses, return-statuses, delivery-statuses, delivery-sources, comment-visibilities, item-types, cancellation-scopes, cancellation-kinds, order-cancellation-kinds, discount-applies-to, discount-placements, discount-sources, discount-effect-kinds, discount-value-types, refund-modes, return-reason-tones and return-resolutions. A value's title is usually an {en, de} map, but it can also be a plain string, so handle both.

In this group