Order lists
An order list is a saved collection of positions a buyer keeps and orders again: a monthly reorder, a reagent list, a labelling list. It is not a cart and not a draft order — it is read, never emptied, so the same list converts again next month.
Base path: https://api.revenexx.com/v1/orderlists.
The list
curl -X POST https://api.revenexx.com/v1/orderlists \
-H "X-Revenexx-Tenant: <TENANT_SLUG>" \
-H "X-Revenexx-Api-Key: rvxk_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Monthly consumables",
"owner_id": "…",
"owner_name": "Anna Berger",
"organization_id": "…",
"kind": "shopping",
"items": [
{ "name": "Cordless drill, black", "sku": "ACME-4711-BLK", "quantity": 6 },
{ "name": "Drill bit set", "sku": "ACME-9001", "quantity": 2 }
]
}'
Three fields are required — name, owner_id, owner_name — and they are exactly the columns the database will not fill in. Everything else has an answer already: kind resolves to your value, else the market's default_kind setting, else the kind the tenant flagged; shared is false; organization_id is null, which makes shared meaningless because there is then nobody to share with.
Nothing about a list is unique. One owner may keep two lists with the same name, and the same article may appear in as many lists as the buyer wants.
The optional items array creates the list pre-filled, in one request rather than a create plus twenty adds — and the array order is the position order. Those initial positions are normalised and article-checked before the list row is written, and both caps are checked first as well (max_items_per_list against the array, max_lists_per_owner against what this contact already keeps), so a rejected position never leaves an empty list behind.
The owner is set once. No route moves a list to another contact.
Positions
curl -X POST "https://api.revenexx.com/v1/orderlists/{list_id}/items" \
-H "X-Revenexx-Tenant: <TENANT_SLUG>" \
-H "X-Revenexx-Api-Key: rvxk_..." \
-H "Content-Type: application/json" \
-d '{"name":"Drill bit set","sku":"ACME-9001","quantity":2,"cost_center_id":"CC-4711"}'
A position is a whole saved line, not a pointer at a product. name is required and one of product_id / sku must be set; everything else is a snapshot of what the buyer saw: quantity, unit, price, tax_rate, image, custom_sku, cost_center_id, position_texts, category_slug, subcategory_slug, metadata.
A new row takes the list's current position count unless the payload names a position, so it collides with an existing number whenever an earlier position was deleted from the middle. Adding one touches the list's updated_at, which is what the default sort of GET /v1/orderlists reads.
| Route | Does |
|---|---|
GET /v1/orderlists/{list_id}/items | List the positions. |
POST /v1/orderlists/{list_id}/items | Add one. |
PUT /v1/orderlists/{list_id}/items | Replace all positions. |
GET/PUT/DELETE /v1/orderlists/{list_id}/items/{id} | One position. |
GET /v1/orderlists/{id} | The list with its positions. |
Kinds
A kind is the tenant's own taxonomy for sorting saved lists. Nothing in the app branches on the value, which is why a merchant can add one without a release.
curl -X POST https://api.revenexx.com/v1/orderlists/kinds \
-H "X-Revenexx-Tenant: <TENANT_SLUG>" \
-H "X-Revenexx-Api-Key: rvxk_..." \
-H "Content-Type: application/json" \
-d '{"code":"reagents","title":"Reagent lists","tone":"info","position":30}'
code and title are required. The code is lowercased on the way in and immutable afterwards — renaming it would orphan every list carrying it, since a list stores the code and not the id. tone is one of neutral, info, success, warning, danger, for the badge a list row shows.
is_default: true promotes the new kind and demotes whoever held the flag; POST /v1/orderlists/kinds/{id}/make-default does the same later. Creating a kind changes no existing list.
POST /v1/orderlists/defaults seeds the two kinds a fresh tenant starts with — shopping (default) and label. Idempotent by code: created names what this call wrote, existing what was already there and was left exactly as the tenant keeps it, renames included. The install event runs it, and the first read of GET /v1/orderlists/kinds on an empty table seeds before answering — so you rarely need it.
Turning a list into a cart
curl -X POST "https://api.revenexx.com/v1/orderlists/{id}/cart" \
-H "X-Revenexx-Tenant: <TENANT_SLUG>" \
-H "X-Revenexx-Api-Key: rvxk_..." \
-H "Content-Type: application/json" \
-d '{"mode":"append"}'
Every position goes into a cart in one call.
- Sending
cart_idadds to that existing cart. - Omitting it creates a cart for the list's owner — not for whoever called — names it after the list, and makes it that owner's current cart. A cart the buyer cannot see is not "added to cart".
Which happened is not left to be inferred: cart_created says so and cart_id names the cart either way.
append (the default, tenant-configurable through cart_merge_mode) lets the carts app merge each line by product and price so quantities accumulate. replace makes the list the cart's whole contents in one call.
What the cart has no column for — cost centre, custom SKU, position texts — rides in each line's snapshot, together with the list it came from.
The list itself is never touched.
Turning a list straight into an order
curl -X POST "https://api.revenexx.com/v1/orderlists/{id}/order" \
-H "X-Revenexx-Tenant: <TENANT_SLUG>" \
-H "X-Revenexx-Api-Key: rvxk_..." \
-H "Content-Type: application/json" \
-d '{"contact_id":"…"}'
Places the list's positions as an order. Buyer and organization come from the list; the cost centre and the position texts land on the order's own columns; the list is left exactly as it stands.
It is the orders app that places it, over the gateway rather than over a shared table, so everything an order means is that app's answer.
The acting contact is re-asserted on the call, so the orders app applies its rules to the buyer rather than to this app: a contact holding only orders.request, or an order above the tenant's approval threshold, comes back with status: "pending" and no placed_at instead of being refused. That pending order is the platform's nearest thing to a draft — the orders app owns the state, which is why status is reported rather than chosen, and why the created order is handed back verbatim under order.
Where to go next
- Carts — where an appended list lands.
- Orders — the placement rules a list order goes through.
- Roles and permissions — why a list order can come back
pending.