Direct sales
Called with warehouse account keys. A key only sees its own account's data. Every example uses the same sample brand, order and item, so you can follow one item from call to call.
http://62.169.23.239:8001/api/v1Auth Bearer token3 endpointsEndpoints
Create a sale#
Requires scope warehouse_sales:write
Record a sale of sealed boxes, loose units, or both on one receipt.
A box line moves the whole carton: the box flips to 'sold' and no per-unit passport changes hands, because the sale does not open it. A line carrying a unit_token is a serialised loose unit, so it transfers real ownership — it requires buyer_email (an existing consumer account), writes the consumer_items row, and queues the custody/activate/certificate legs on chain. A line with just sku and quantity claims that many anonymous loose units, oldest first.
Sale, stock and passports share one transaction — none of them commits without the others.
409 if a box is not sealed and held here, if a referenced unit is not loose in stock, or if there are fewer loose units than a quantity line asks for. 422 if the warehouse has no country set (VAT cannot be resolved).
Request body
- source_terminalstring | null
- destinationstring | null
- line_itemsarray of objectsRequired
What was sold.
+Show 8 child attributesHide child attributes
- line_kindstring
unit = one loose item; box = a whole sealed box.
One ofboxunitDefaults to
unit - box_idstring | null
Id printed on the box label. Scanning the box resolves to this id.
- unit_tokenstring | null
The item's own token. It is what the item's QR code carries, and the id every item event uses.
- product_namestringRequired
- variant_labelstring | null
- skustring | null
Stock-keeping unit of the variant.
- unit_price_eurnumberRequired
Price of one unit in euro, including VAT.
- quantityinteger
Number of units.
Defaults to
1
- discount_kindstring | nullOne of
percentamount - discount_valuenumber | null
0-100 when discount_kind is 'percent', a euro amount when 'amount'. Applied to the subtotal before VAT.
- buyer_emailstring | null
Buyer's account email. Required when any line carries a unit_token or sells a whole box — ownership of every unit sold transfers to this account, so it cannot be sold into no account (a passport orphan). Only anonymous quantity lines (sku + quantity, no unit_token) may omit it.
Returns
HTTP 201- sale_idstringRequired
- source_terminalstring | null
- destinationstring | null
- processed_attimestampRequired
- statusstringRequired
Current state of the object. See the allowed values.
One ofcompletedprocessingcancelled - line_itemsarray of objects
What was sold.
+Show 11 child attributesHide child attributes
- line_kindstring
unit = one loose item; box = a whole sealed box.
One ofboxunitDefaults to
unit - box_idstring | null
Id printed on the box label. Scanning the box resolves to this id.
- unit_tokenstring | null
The item's own token. It is what the item's QR code carries, and the id every item event uses.
- product_namestringRequired
- variant_labelstring | null
- skustring | null
Stock-keeping unit of the variant.
- unit_price_eurnumber
Price of one unit in euro, including VAT.
Defaults to
0 - quantityinteger
Number of units.
Defaults to
1 - subtotal_eurnumber
Defaults to
0 - unit_countinteger
Physical units this line moved. For a box line this is the box's unit_count, which is why it can exceed quantity (1 box, 120 units).
Defaults to
0 - consumer_item_idinteger | null
- box_countinteger
Defaults to
0 - unit_countinteger
Defaults to
0 - subtotal_eurnumber
Defaults to
0 - discount_kindstring | nullOne of
percentamount - discount_valuenumber | null
- discount_eurnumber
Defaults to
0 - vat_rate_pctnumber
Defaults to
0 - vat_countrystring | null
- vat_eurnumber
Defaults to
0 - total_eurnumber
Total paid in euro, including VAT.
Defaults to
0 - buyer_consumer_idstring | null
- storeobject | null
Warehouse identity for the receipt, from retail_profiles — a Warehouse account shares that table with a shop (middleware.auth STOCK_HOLDING_ROLES); there is no separate warehouse_profiles table.
+Show 5 child attributesHide child attributes
- store_namestring | null
- citystring | null
- countrystring | null
- glnstring | null
- emailstring | null
- buyerobject | null
+Show 3 child attributesHide child attributes
- consumer_idstring | null
- namestring | null
- emailstring | null
curl -X POST http://62.169.23.239:8001/api/v1/warehouse/sales \
-H "Authorization: Bearer $SKANNIT_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"buyer_email": "customer@example.com",
"source_terminal": "wh-desk-1",
"line_items": [
{
"line_kind": "box",
"box_id": "BOX-F4E1A9-0003",
"product_name": "Trail Tee",
"unit_price_eur": 22.0
},
{
"line_kind": "unit",
"unit_token": "Qm7xK2pL9vR4tY8wZ1nB3c",
"product_name": "Trail Tee",
"variant_label": "White / M",
"quantity": 1,
"unit_price_eur": 29.0
}
]
}'{
"sale_id": "beb26697-234b-56da-9552-60c999feed1f",
"source_terminal": "till-1",
"destination": "in_store",
"processed_at": "2026-10-09T09:30:00Z",
"status": "completed",
"line_items": [
{
"line_kind": "box",
"box_id": "BOX-F4E1A9-0003",
"unit_token": "Qm7xK2pL9vR4tY8wZ1nB3c",
"product_name": "Trail Tee",
"variant_label": "White / M",
"sku": "TT-100-WHT-M",
"unit_price_eur": 29,
"quantity": 1,
"subtotal_eur": 29,
"unit_count": 24,
"consumer_item_id": "7bf9462f-3324-593d-9012-c800e102b014"
}
],
"box_count": 24,
"unit_count": 24,
"subtotal_eur": 29,
"discount_kind": "percent",
"discount_value": 24,
"discount_eur": 0,
"vat_rate_pct": 21,
"vat_country": "NL",
"vat_eur": 5.03,
"total_eur": 29,
"buyer_consumer_id": "b3a40984-b53d-50bf-b3de-f096ab2c40a8",
"store": {
"store_name": "Northwind Amsterdam",
"city": "Rotterdam",
"country": "NL",
"gln": "8712345000004",
"email": "ops@northwind.example"
},
"buyer": {
"consumer_id": "f4811030-eecf-579d-a105-e02ee601e044",
"name": "Northwind Apparel",
"email": "ops@northwind.example"
}
}List sales#
Requires scope warehouse_sales:read
Recent sales, newest first, with box and unit counts per sale.
Returns
HTTP 200- itemsarray of objects
The objects on this page.
+Show 8 child attributesHide child attributes
- sale_idstringRequired
- source_terminalstring | null
- destinationstring | null
- processed_attimestampRequired
- statusstringRequired
Current state of the object. See the allowed values.
One ofcompletedprocessingcancelled - box_countinteger
Defaults to
0 - unit_countinteger
Defaults to
0 - total_eurnumber
Total paid in euro, including VAT.
Defaults to
0
- totalinteger
Total count.
Defaults to
0
curl http://62.169.23.239:8001/api/v1/warehouse/sales \
-H "Authorization: Bearer $SKANNIT_TOKEN"{
"items": [
{
"sale_id": "beb26697-234b-56da-9552-60c999feed1f",
"source_terminal": "till-1",
"destination": "in_store",
"processed_at": "2026-10-09T09:30:00Z",
"status": "completed",
"box_count": 24,
"unit_count": 24,
"total_eur": 29
}
],
"total": 1
}Retrieve a sale#
Requires scope warehouse_sales:read
Single sale detail — line items, VAT, totals, buyer and receipt header.
Parameters
- sale_idstringpathRequired
Returns
HTTP 200- sale_idstringRequired
- source_terminalstring | null
- destinationstring | null
- processed_attimestampRequired
- statusstringRequired
Current state of the object. See the allowed values.
One ofcompletedprocessingcancelled - line_itemsarray of objects
What was sold.
+Show 11 child attributesHide child attributes
- line_kindstring
unit = one loose item; box = a whole sealed box.
One ofboxunitDefaults to
unit - box_idstring | null
Id printed on the box label. Scanning the box resolves to this id.
- unit_tokenstring | null
The item's own token. It is what the item's QR code carries, and the id every item event uses.
- product_namestringRequired
- variant_labelstring | null
- skustring | null
Stock-keeping unit of the variant.
- unit_price_eurnumber
Price of one unit in euro, including VAT.
Defaults to
0 - quantityinteger
Number of units.
Defaults to
1 - subtotal_eurnumber
Defaults to
0 - unit_countinteger
Physical units this line moved. For a box line this is the box's unit_count, which is why it can exceed quantity (1 box, 120 units).
Defaults to
0 - consumer_item_idinteger | null
- box_countinteger
Defaults to
0 - unit_countinteger
Defaults to
0 - subtotal_eurnumber
Defaults to
0 - discount_kindstring | nullOne of
percentamount - discount_valuenumber | null
- discount_eurnumber
Defaults to
0 - vat_rate_pctnumber
Defaults to
0 - vat_countrystring | null
- vat_eurnumber
Defaults to
0 - total_eurnumber
Total paid in euro, including VAT.
Defaults to
0 - buyer_consumer_idstring | null
- storeobject | null
Warehouse identity for the receipt, from retail_profiles — a Warehouse account shares that table with a shop (middleware.auth STOCK_HOLDING_ROLES); there is no separate warehouse_profiles table.
+Show 5 child attributesHide child attributes
- store_namestring | null
- citystring | null
- countrystring | null
- glnstring | null
- emailstring | null
- buyerobject | null
+Show 3 child attributesHide child attributes
- consumer_idstring | null
- namestring | null
- emailstring | null
curl http://62.169.23.239:8001/api/v1/warehouse/sales/beb26697-234b-56da-9552-60c999feed1f \
-H "Authorization: Bearer $SKANNIT_TOKEN"{
"sale_id": "beb26697-234b-56da-9552-60c999feed1f",
"source_terminal": "till-1",
"destination": "in_store",
"processed_at": "2026-10-09T09:30:00Z",
"status": "completed",
"line_items": [
{
"line_kind": "box",
"box_id": "BOX-F4E1A9-0003",
"unit_token": "Qm7xK2pL9vR4tY8wZ1nB3c",
"product_name": "Trail Tee",
"variant_label": "White / M",
"sku": "TT-100-WHT-M",
"unit_price_eur": 29,
"quantity": 1,
"subtotal_eur": 29,
"unit_count": 24,
"consumer_item_id": "7bf9462f-3324-593d-9012-c800e102b014"
}
],
"box_count": 24,
"unit_count": 24,
"subtotal_eur": 29,
"discount_kind": "percent",
"discount_value": 24,
"discount_eur": 0,
"vat_rate_pct": 21,
"vat_country": "NL",
"vat_eur": 5.03,
"total_eur": 29,
"buyer_consumer_id": "b3a40984-b53d-50bf-b3de-f096ab2c40a8",
"store": {
"store_name": "Northwind Amsterdam",
"city": "Rotterdam",
"country": "NL",
"gln": "8712345000004",
"email": "ops@northwind.example"
},
"buyer": {
"consumer_id": "f4811030-eecf-579d-a105-e02ee601e044",
"name": "Northwind Apparel",
"email": "ops@northwind.example"
}
}